Runner

原始碼:Lib/asyncio/runners.py

這個章節概述用於執行 asyncio 程式碼的高階 asyncio 原語。

他們是基於一個事件迴圈,目的是為了簡化常見且廣泛運用場景的非同步程式碼。

執行 asyncio 程式

asyncio.run(coro, *, debug=None, loop_factory=None)

在 asyncio 事件迴圈中執行 coro 並回傳結果。

該引數可以是任何可等待物件 (awaitable object)。

這個函式負責執行傳入的可等待物件、管理 asyncio 的事件迴圈、最終化非同步產生器以及關閉執行器。

當另一個非同步事件迴圈在同一執行緒中執行時,無法呼叫此函式。

如果 debugTrue,事件迴圈會以除錯模式執行。False 則會關閉除錯模式。None 則會優先使用除錯模式的全域設定。

如果 loop_factory 不為 None,它會被用於建立一個新的事件迴圈;否則會改用 asyncio.new_event_loop()。迴圈會在最後關閉。這個函式應該要作為asyncio 程式的主要進入點,且理想上僅會被呼叫一次。推薦使用 loop_factory 來設定事件迴圈時而不是使用 policies(政策)。傳遞 asyncio.EventLoop 可以讓 asyncio 在沒有政策系統的情況下運行。

執行器的關閉逾時期限為 5 分鐘。如果執行器未在這段時間內完成,系統會發出警告並關閉執行器。

範例:

async def main():
    await asyncio.sleep(1)
    print('hello')

asyncio.run(main())

在 3.7 版被加入.

在 3.9 版的變更: 更新為使用 loop.shutdown_default_executor()

在 3.10 版的變更: debug 預設為 None,以遵循全域偵錯模式設定。

在 3.12 版的變更: 新增 loop_factory 參數。

在 3.14 版的變更: coro 可以是任何可等待物件。

備註

asyncio 的政策系統已被棄用,並將於 Python 3.16 移除;此後必須明確指定 loop_factory 才能設定事件迴圈。

Runner 情境管理器

class asyncio.Runner(*, debug=None, loop_factory=None)

此情境管理器可簡化在相同情境中對多個非同步函式的呼叫。

有時需要在相同的 事件迴圈contextvars.Context 中呼叫數個頂層非同步函式。

如果 debugTrue,事件迴圈會以除錯模式執行。False 則會關閉除錯模式。None 則會優先使用除錯模式的全域設定。

loop_factory 可用來覆寫事件迴圈的建立方式。loop_factory 必須負責將建立的迴圈設定為目前的事件迴圈。若 loop_factoryNone,預設會使用 asyncio.new_event_loop(),並以 asyncio.set_event_loop() 將其設定為目前的事件迴圈。

基本上,asyncio.run() 的範例可以改寫為使用 Runner:

async def main():
    await asyncio.sleep(1)
    print('hello')

with asyncio.Runner() as runner:
    runner.run(main())

在 3.11 版被加入.

run(coro, *, context=None)

在內嵌的事件迴圈中執行 coro

該引數可以是任何可等待物件 (awaitable object)。

如果引數是協程,會將其包裝在 Task 中。

可選的僅限關鍵字引數 context,可用來指定程式碼執行時所使用的自訂 contextvars.Context。如果 context 為 None,則使用 Runner 的預設情境。

回傳可等待物件的結果,或引發例外。

當另一個非同步事件迴圈在同一執行緒中執行時,無法呼叫此函式。

在 3.14 版的變更: coro 可以是任何可等待物件。

close()

關閉 Runner。

最終化非同步產生器、關閉預設執行器與事件迴圈,並釋放內嵌的 contextvars.Context

get_loop()

回傳與 Runner 實例關聯的事件迴圈。

備註

Runner 採用延遲初始化策略,其建構函式不會初始化底層的低階結構。

內嵌的 loopcontext 會在進入 with 陳述式主體,或第一次呼叫 run()get_loop() 時建立。

處理鍵盤中斷

在 3.11 版被加入.

signal.SIGINTCtrl-C 引發時,預設會在主執行緒中引發 KeyboardInterrupt 例外。然而,此方式不適用於 asyncio,因為這可能會中斷 asyncio 內部運作,並造成程式無法結束。

為了緩解此問題,asyncio 會依下列方式處理 signal.SIGINT

  1. asyncio.Runner.run() 會在執行任何使用者程式碼之前安裝自訂的 signal.SIGINT 處理程式,並在函式結束時將其移除。

  2. Runner 會為傳入的協程建立主 Task,以執行該協程。

  3. signal.SIGINTCtrl-C 引發時,自訂訊號處理程式會呼叫 asyncio.Task.cancel() 來取消主 Task,而此方法會在主 Task 內引發 asyncio.CancelledError。這會使 Python 堆疊展開,並可使用 try/excepttry/finally 區塊來清理資源。主 Task 取消後,asyncio.Runner.run() 會引發 KeyboardInterrupt

  4. 使用者可能會撰寫一個無法由 asyncio.Task.cancel() 中斷的緊密迴圈;在此情況下,再次按下 Ctrl-C 會立即引發 KeyboardInterrupt,而不會取消主 Task。