asyncio 的使用姿勢

前段時間使用 asyncio 寫了一個小程序,摸索出一些使用上的注意事項,記錄如下。

本質

有人把 asyncio 這類東西叫做使用同步語法的異步程序,即說明它仍然是異步程序的本質,只不過在語法層面進行了優化,避免陷入回調地獄。取代回調的是 asyncawait 關鍵字和coroutineFuture 對象。

Future 的意思是未來,即尚未發生之事。coroutine 算是一種自主調度的程序執行模式。對標到回調上,我理解的 Future 就像是回調地獄裏的葉子節點,coroutine 算是非葉子節點。因爲 coroutine 可以等待 Future,也可以等待其他 coroutine。

await 即 asynchronous wait,是等待事件發生的意思。async 關鍵字不單獨使用,而是作爲一個副詞修飾其他關鍵字,比如async def定義一個 coroutine,async for用於異步迭代,async with用於異步進入上下文。

coroutine 和生成器之間有着千絲萬縷的聯繫。因爲 Python 中原生能夠實現調用後不立即執行功能的就是生成器,所以早期的三方 coroutine 都是基於生成器做的。通過 yield 進行調出,再使用 .send() 調入。Python3.5 以後有了原生的定義和執行 coroutine 的關鍵字就是 async def 和 await。但基於生成器的語法也被保留,即 @asyncio.coroutineyield from,不過不允許混用。如果有條件使用 3.5 或更新的版本,建議儘量使用新的關鍵字,因爲更加清晰。

由上可見,多層嵌套的回調執行模式本質上仍然保留,只不過不再出現在源代碼上,因此便稱不上“地獄”了。

time()

當需要使用時間戳的時候,比如 call_at(),需要注意,loop 有一個自己的 loop.time()。他的時間比標準時間戳要小,且會迴繞。當用在跟 loop 相關的時間控制時一定要用 loop.time() ,time.time() 只應該用在與 loop 無關的地方,比如業務層或者寫日誌之類的。

timeout waiter

asyncio 自帶的一個超時控制器,不必自己寫了,添加註釋後的源碼如下:

@coroutine
def wait_for(fut, timeout, *, loop=None):
    """Wait for the single Future or coroutine to complete, with timeout.

    Coroutine will be wrapped in Task.

    Returns result of the Future or coroutine.  When a timeout occurs,
    it cancels the task and raises TimeoutError.  To avoid the task
    cancellation, wrap it in shield().

    If the wait is cancelled, the task is also cancelled.

    This function is a coroutine.
    """
    if loop is None:
        loop = events.get_event_loop()

    if timeout is None:
        return (yield from fut)

    waiter = loop.create_future()
    timeout_handle = loop.call_later(timeout, _release_waiter, waiter)  # finish waiter
    cb = functools.partial(_release_waiter, waiter)

    fut = ensure_future(fut, loop=loop)
    fut.add_done_callback(cb)  # 若按時完成,則 finish waiter

    try:
        # wait until the future completes or the timeout
        try:
            yield from waiter  # 等待 waiter finished
        except futures.CancelledError:  # wait_for 被 cancel
            fut.remove_done_callback(cb)
            fut.cancel()
            raise

        if fut.done():  # 若按時完成
            return fut.result()
        else:  # 超時
            fut.remove_done_callback(cb)
            fut.cancel()
            raise futures.TimeoutError()
    finally:
        timeout_handle.cancel()

run_forever()

run_forever() 不接受參數,如果你想跑一個服務類的進程,需要先使用 ensure_future() 之類的添加一個起始 coroutine,並且保證總是有的跑,不會退出。

cancel()

cancel() 一個 coroutine 時,它正在 await 的 coroutine 也會被 cancel。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章