一、前述
通過上一篇的Configurable類的相關介紹,這裏再進一步說明IOLoop類,其類的註釋也說明了,在Tornado 6.0版本中,Tornado的IOLoop類只是對asyncio event loop的包裝而已。也就是說本質上就是asyncio庫。爲了方便查看IOLoop類相應的類關係。這裏也放置一張類圖。
從圖中可以看到,IOLoop是直接繼承至Configurable類的。在IOLoop中,主要完成的基本任務如下:
- 重載實現Configurable的接口,定義基本的基類和默認的實現類。
- 定義功能接口。該接口主要是提供給上層調用,同時也規定其子類必須要實現的功能接口。
與此同時,需要了解的是,對於一個套接字,當對其進行狀態監聽時,該套接字可能觸發的狀態有:可讀、可寫、出錯;對於網絡模型中套接字的監聽,一般是通過註冊一個回調處理函數,在相應的狀態變化時,通過調用對應的回調函數進行處理。asyncio對於特定的網絡套接字的監聽也是通過這樣的手段。asyncio運行所有的異步實現都是在event loop上,一般來說,對於主線程,在沒有當前沒有event loop實例時,會調用默認的事件策略去生成一個,並設置爲當前event loop;但是對於非主線程,並不會主動生成一個事件循環實例,而是需要用戶自己實例化一個。考慮到這個情況,Tornado在實現上,實現了兩個異步IO循環實例類,即AsycIOMainLoop和AsyncIOLoop。
爲了方便說明,約定IOLoop指的是Tornado的IOLoop類或者子類,而ioloop指的是asyncio庫的event loop。
二、IOLoop類
-
重載實現Configurable的接口
(1)configure方法,Tornado對該方法只是添加了類型檢查判斷而已。
(2)configurable_base,定義返回的直接基類是IOLoop類
(3)configurable_default,定義默認實例化的子類的是AsyncIOLoop類,這個子類在每次實例化時,都會生成一個新的asyncio的event loop實例,用戶可通過實例化時,指定make_current參數,來設置要不要使用新的event loop作爲當前的事件循環實例。(這點下面再細說怎麼用)
(4)initialize,實現初始化方法,可指定make_current參數,設置當前的Event 事件循環。 -
定義功能接口
這裏定義了一系列接口,基本上與asyncio庫的高層用戶接口差不多,只是Tornado作了更多的邏輯優化處理而已;除此之外,還有一些靜態方法方便用戶調用。
方便用戶調用的方法,如instance、clear_instance、current、clear_current等。
比如看下current方法
@staticmethod
def current(instance: bool = True) -> Optional["IOLoop"]: # noqa: F811
"""Returns the current thread's `IOLoop`.
返回當前線程的IOLoop實例
"""
# 嘗試獲取asyncio的ioloop實例
loop = asyncio.get_event_loop()
except (RuntimeError, AssertionError):
if not instance:
return None
raise
try:
# 嘗試獲取與當前asyncio的ioloop相對應的Tornado的IOLoop類實例
# 這裏的_ioloop_for_asyncio是一個類變量,屬於字典類型,鍵是asyncio的ioloop實例,值是Tornado的IOLoop實例
return IOLoop._ioloop_for_asyncio[loop]
except KeyError:
# 走到這裏表示,當前有asyncio的ioloop,但是還沒有相對應的IOLoop實例,因此,若在調用current時,指定了instance,指示在沒有IOLoop時,就創建一個,則這裏就實例化創建一個實現類AsyncMainIOLoop。
if instance:
from tornado.platform.asyncio import AsyncIOMainLoop
current = AsyncIOMainLoop(make_current=True) # type: Optional[IOLoop]
else:
current = None
return current
其它的功能接口定義,如add_handler、update_handler、remove_handler、start、stop等,則由其直接子類BaseAsyncIOLoop來實現。
三、BaseAsyncIOLoop類
首先這個類重載實現了自己的初始化過程,即initialize方法,在該方法上,可以看到其有一些相應的實例變量。這裏簡單介紹如下:
def initialize( # type: ignore
self, asyncio_loop: asyncio.AbstractEventLoop, **kwargs: Any
) -> None:
# 這裏記錄下,與當前類相對的asyncio的ioloop實例。這樣在後面需要用到asyncio的ioloop實例時,
# 不必每次都去_ioloop_for_asyncio這個類字典變量中查閱,可以提高程序的執行速度,也算是一種優化吧。
self.asyncio_loop = asyncio_loop
# Maps fd to (fileobj, handler function) pair (as in IOLoop.add_handler)
# type: Dict[int, Tuple[Union[int, _Selectable], Callable]]
# 這裏的英文註釋就很清晰了,這是一個維護相應的fd狀態變化時的回調處理函數的一個映射表
self.handlers = {}
# Set of fds listening for reads/writes
# 下面這兩個集合是分別對可讀或者可寫狀態進行監聽的fd的集合
#
self.readers = set()
self.writers = set()
self.closing = False
# 這個是一個健全性判斷處理。因爲Tornado的IOLoop是對asyncio的ioloop的包裝實現。也就是說當調用asyncio的close相關的方法時,也能正常停止異步相關的功能,但是對於Tornado來說,並不知道asyncio已經被停止,從而導致_ioloop_for_asyncio中存在未正確釋放的映射鍵值對。因此這裏在實現化時,添加一個檢查,去掉已關閉的ioloop映射。
for loop in list(IOLoop._ioloop_for_asyncio):
if loop.is_closed():
del IOLoop._ioloop_for_asyncio[loop]
IOLoop._ioloop_for_asyncio[asyncio_loop] = self
self._thread_identity = 0
# 往上初始化,即初始化IOLoop類的initialize方法
super(BaseAsyncIOLoop, self).initialize(**kwargs)
def assign_thread_identity() -> None:
self._thread_identity = get_ident()
# 在異步操作啓動後,獲取記錄下本線程的標識值,以便於在需要判斷線程安全的地方,調用線程安全的接口實現,如add_callback方法
self.add_callback(assign_thread_identity)
實現IOLoop定義的接口。
- add_handler方法,添加相應的fd監聽類型和註冊對應的回調處理函數
- update_handler方法,更新特定fd的監聽類型
- remove_handler方法,去年特定fd的監聽
- start方法,啓動運行asyncio的ioloop事件循環,即關鍵點就是self.asyncio_loop.run_forever()
- stop方法,即停止asyncio的io事件循環
- close方法,關閉所有的asyncio的io事件循環。
- add_callback方法,這裏Tornado會根據調用者與運行線程的關係,安全的判斷,並調用線程安全的回調函數。
- 其它方法,這裏就不一一介紹了。
四、實現類AsyncIOMainLoop和AsyncIOLoop
調用實例化AsyncIOMainLoop時,一般代表着正在創建一個IOLoop類,使其對應於asyncio的event loop事件,換句話說,即當前是一定有asyncio的event loop時,纔會嘗試去實例化這個類。
而AsyncIOLoop類的區別點是,其在每次的初始化initialize方法中,都會創建一個asyncio類的event loop實例,然後使得AsyncIOLoop的實例與這個新建的event loop相關聯,但是這裏要注意,當這樣使用時,要配合添加make_current參數爲True,這樣就能使這個新的event loop得到正常使用。
對於AsyncIOLoop類來說,初始化過程主要是涉及其重載實現的initialize方法和make_current方法。
在其initialize中調用到了loop = asyncio.new_event_loop(),而在make_current方法中調用到了asyncio.set_event_loop(self.asyncio_loop)。從而使得新創建的asyncio可以正常使用。
五、使用方法
- 對於在主線程中運行Tornado時
import tornado.ioloop
# 調用類方法current,這裏會去查找判斷asyncio的ioloop所對應的IOLoop實例,
# 若沒有則實例化一個AsyncIOMainLoop與之對應。當然也可以調用intance方法。
tornado.ioloop.IOLoop.current().start()
- 對於在子線程中運行Tornado時
import tornado.ioloop
import asyncio
# 方法一:因爲IOLoop默認使用AsyncIOLoop類,因此可以使用make_current參數,
# 確保新建的asyncio的event loop類可以使用。
tornado.ioloop.IOLoop(make_current=True).start()
# 方法二:先手動創建並設置當前的asyncio類的ioloop實例,然後調用current方法,
# 來實例出一個AsyncIOMainLoop類實例與這個手動創建的asyncio的ioloop相關聯
io_loop = asyncio.new_event_loop()
asyncio.set_event_loop(io_loop)
tornado.ioloop.IOLoop.current().start()