事件迴圈

原始碼: Lib/asyncio/events.pyLib/asyncio/base_events.py


前言

事件迴圈是每個 asyncio 應用程式的核心。事件迴圈執行非同步任務和回呼、執行網路 IO 操作並啟動子行程。

應用程式開發人員通常應使用高階的 asyncio 函式,例如 asyncio.run(),並且很少需要參照事件迴圈物件或呼叫其方法。本節主要針對那些需要更細粒度控制事件迴圈行為的低階程式碼、函式庫和框架的作者。

取得事件迴圈

以下的低階函式可用於取得、設置或建立事件迴圈:

asyncio.get_running_loop()

在目前作業系統執行緒中回傳正在運行的事件迴圈。

如果沒有運行的事件迴圈,則引發 RuntimeError

此函式只能從協程或回呼函式中呼叫。

在 3.7 版被加入.

asyncio.get_event_loop()

取得目前的事件迴圈。

當從協程或回呼函式呼叫此函式(例如,使用 call_soon 或類似的 API 於排程呼叫),此函式將永遠回傳正在運行的事件迴圈。

如果沒有設定正在運行的事件迴圈,該函式將回傳 get_event_loop_policy().get_event_loop() 呼叫的結果。

由於此函式具有相當複雜的行為(尤其是在使用自訂事件迴圈策略時),在協程和回呼函式中,建議使用 get_running_loop() 函式,而不是 get_event_loop()

如上所述,可以考慮使用高階的 asyncio.run() 函式,而不是使用這些較低階的函式手動建立和關閉事件迴圈。

在 3.14 版的變更: 如果沒有目前的事件迴圈,則引發 RuntimeError

備註

asyncio 的政策系統已被棄用,並將於 Python 3.16 中移除;此後,此函式將回傳目前執行中的事件迴圈(若存在),否則將回傳由 set_event_loop() 設定的迴圈。

asyncio.set_event_loop(loop)

loop 設置為目前 OS 執行緒的目前事件迴圈。

asyncio.new_event_loop()

建立並回傳新的事件迴圈物件。

請注意 get_event_loop()set_event_loop()new_event_loop() 函式的行為可以透過設定自訂事件迴圈策略進行調整。

目錄

本頁文件包含以下章節:

事件迴圈方法

事件迴圈提供以下低階 API:

啟動和停止迴圈

loop.run_until_complete(future)

運行直到 future (一個 Future 實例)完成。

如果引數是協程物件,則它將被隱式排程為 asyncio.Task 運行。

回傳 Future 的結果或引發其例外。

loop.run_forever()

運行事件迴圈直到 stop() 被呼叫。

如果在呼叫 run_forever() 之前呼叫 stop(),則迴圈將使用超時為零的方式輪詢 I/O 選擇器,運行所有回應 I/O 事件(以及已經排程的事件)的回呼,然後退出。

如果在 run_forever() 運行時呼叫 stop(),則迴圈將運行目前批次的回呼函式,然後退出。請注意,由回呼函式排程的新回呼在此情況下不會運行;而是在下次呼叫 run_forever()run_until_complete() 時運行。

loop.stop()

停止事件迴圈。

loop.is_running()

如果事件迴圈目前正在運行,則回傳 True

loop.is_closed()

如果事件迴圈已關閉,則回傳 True

loop.close()

關閉事件迴圈。

不得於迴圈運行中呼叫此函式。將丟棄任何待處理的回呼。

此方法清除所有佇列並關閉執行器,但不等待執行器完成。

此方法是冪等且不可逆的。在事件迴圈關閉後不應呼叫其他方法。

async loop.shutdown_asyncgens()

排程所有目前打開的非同步產生器物件使用 aclose() 呼叫來關閉。呼叫此方法後,如果疊代新的非同步產生器,事件迴圈將發出警告。應該使用此方法可靠地完成所有已排程的非同步產生器。

請注意,使用 asyncio.run() 時不需要呼叫此函式。

範例:

try:
    loop.run_forever()
finally:
    loop.run_until_complete(loop.shutdown_asyncgens())
    loop.close()

在 3.6 版被加入.

async loop.shutdown_default_executor(timeout=None)

排程預設執行器的關閉,並等待它加入 ThreadPoolExecutor 中的所有執行緒。一旦呼叫了此方法,使用預設執行器與 loop.run_in_executor() 將引發 RuntimeError

timeout 參數指定執行器完成加入所需的時間(以 float 秒為單位)。預設情況下為 None,不會限制執行器所花費的時間。

如果達到 timeout,將發出 RuntimeWarning 警告,預設執行器將立即終止,不等待其執行緒完成加入。

備註

使用 asyncio.run() 時請勿呼叫此方法,因為後者會自動處理預設執行器的關閉。

在 3.9 版被加入.

在 3.12 版的變更: 加入 timeout 參數。

排程回呼函式

loop.call_soon(callback, *args, context=None)

在事件迴圈的下一次疊代中排程以 args 引數呼叫 callback callback

回傳 asyncio.Handle 的實例,稍後可以用於取消回呼函式。

回呼函式按照其註冊的順序呼叫。每個回呼函式將被呼叫恰好一次。

選用的僅限關鍵字引數 context 指定了要給 callback 執行的自訂 contextvars.Context。當未提供 context 時,回呼函式使用目前情境。

call_soon_threadsafe() 不同,此方法不是執行緒安全的。

loop.call_soon_threadsafe(callback, *args, context=None)

這是 call_soon() 的執行緒安全變體。當從另一個執行緒排程回呼函式時,必須使用此函式,因為 call_soon() 不是執行緒安全的。

This function is safe to be called from a reentrant context or signal handler, however, it is not safe or fruitful to use the returned handle in such contexts.

如果在已關閉的迴圈上呼叫,則引發 RuntimeError。在主應用程式關閉時,這可能發生在次要執行緒上。

請參閱文件的並行和多執行緒部分。

在 3.7 版的變更: 新增了 context 僅限關鍵字參數。詳細資訊請參閱 PEP 567

備註

大多數 asyncio 排程函式不允許傳遞關鍵字引數。要傳遞關鍵字引數,請使用 functools.partial()

# 將會排程 "print("Hello", flush=True)"
loop.call_soon(
    functools.partial(print, "Hello", flush=True))

通常使用 partial 物件比使用 lambda 更方便,因為 asyncio 可以在除錯和錯誤訊息中更好地呈現 partial 物件。

排程延遲的回呼函式

事件迴圈提供為回呼函式排程在將來某個時間點才呼叫的機制。事件迴圈使用了單調時鐘來追蹤時間。

loop.call_later(delay, callback, *args, context=None)

排程 callback 在給定的 delay 秒數後呼叫(可以是整數或浮點數)。

回傳 asyncio.TimerHandle 的實例,可用於取消回呼函式。

callback 將只被呼叫恰好一次。如果有兩個回呼函式被排程在完全相同的時間,則其呼叫順序是不定的。

可選的位置引數 args 將在回呼函式被呼叫時傳遞給它。使用 functools.partial() 來傳遞關鍵字引數callback

可選的僅限關鍵字 context 引數允許為 callback 指定自訂的 contextvars.Context 以提供運行。當未提供 context 時,將使用目前情境。

備註

For performance, callbacks scheduled with loop.call_later() may run up to one clock-resolution early (see time.get_clock_info('monotonic').resolution).

在 3.7 版的變更: 新增了 context 僅限關鍵字參數。詳細資訊請參閱 PEP 567

在 3.8 版的變更: 在 Python 3.7 及更早版本中,使用預設事件迴圈實作時,delay 不能超過一天。這在 Python 3.8 中已經修復。

loop.call_at(when, callback, *args, context=None)

排程 callback 在給定的絕對時間戳 when (整數或浮點數)處呼叫,使用與 loop.time() 相同的時間參照。

此方法的行為與 call_later() 相同。

回傳 asyncio.TimerHandle 的實例,可用於取消回呼函式。

備註

For performance, callbacks scheduled with loop.call_at() may run up to one clock-resolution early (see time.get_clock_info('monotonic').resolution).

在 3.7 版的變更: 新增了 context 僅限關鍵字參數。詳細資訊請參閱 PEP 567

在 3.8 版的變更: 在 Python 3.7 及更早版本中,使用預設事件迴圈實作時,when 和目前時間之間的差值不能超過一天。這在 Python 3.8 中已經修復。

loop.time()

根據事件迴圈的內部單調時鐘,回傳目前時間,以 float 值表示。

備註

在 3.8 版的變更: 在 Python 3.7 及更早版本中,超時(相對 delay 或絕對 when)不應超過一天。這在 Python 3.8 中已經修復。

也參考

函式 asyncio.sleep()

建立 Futures 和 Tasks

loop.create_future()

建立附加到事件迴圈的 asyncio.Future 物件。

這是在 asyncio 中建立 Futures 的首選方式。這允許第三方事件迴圈提供 Future 物件的替代實作(具有更好的性能或儀器計測表現)。

在 3.5.2 版被加入.

loop.create_task(coro, *, name=None, context=None, eager_start=None, **kwargs)

排程執行協程 coro。回傳 Task 物件。

第三方事件迴圈可以使用其自己的 Task 子類別以實現互操作性(interoperability)。在這種情況下,結果類型是 Task 的子類別。

The full function signature is largely the same as that of the Task constructor (or factory) - all of the keyword arguments to this function are passed through to that interface.

如果提供了 name 引數且不為 None,則將其設置為任務的名稱,使用 Task.set_name()

可選的僅限關鍵字 context 引數允許為 coro 指定自訂的 contextvars.Context 以提供運行。當未提供 context 時,將建立目前情境的副本。

An optional keyword-only eager_start argument allows specifying if the task should execute eagerly during the call to create_task, or be scheduled later. If eager_start is not passed the mode set by loop.set_task_factory() will be used.

在 3.8 版的變更: 加入 name 參數。

在 3.11 版的變更: 加入 context 參數。

在 3.13.3 版的變更: Added kwargs which passes on arbitrary extra parameters, including name and context.

在 3.13.4 版的變更: Rolled back the change that passes on name and context (if it is None), while still passing on other arbitrary keyword arguments (to avoid breaking backwards compatibility with 3.13.3).

在 3.14 版的變更: All kwargs are now passed on. The eager_start parameter works with eager task factories.

loop.set_task_factory(factory)

設置將由 loop.create_task() 使用的任務工廠。

如果 factoryNone,將設置預設的任務工廠。否則,factory 必須是一個具有匹配簽名 (loop, coro, **kwargs)callable,其中 loop 是有效事件迴圈的參照、coro 是一個協程物件。該可呼叫物件必須傳遞所有 kwargs 並回傳一個與 asyncio.Task 相容的物件。

在 3.13.3 版的變更: Required that all kwargs are passed on to asyncio.Task.

在 3.13.4 版的變更: name is no longer passed to task factories. context is no longer passed to task factories if it is None.

在 3.14 版的變更: name and context are now unconditionally passed on to task factories again.

loop.get_task_factory()

回傳任務工廠,如果使用預設任務工廠則回傳 None

打開網路連線

async loop.create_connection(protocol_factory, host=None, port=None, *, ssl=None, family=0, proto=0, flags=0, sock=None, local_addr=None, server_hostname=None, ssl_handshake_timeout=None, ssl_shutdown_timeout=None, happy_eyeballs_delay=None, interleave=None, all_errors=False)

打開以 hostport 指定之給定地址的串流傳輸連線。

根據 host(或提供的 family 引數)的情況,socket 家族可以是 AF_INETAF_INET6

Socket 類型將為 SOCK_STREAM

protocol_factory 必須是一個回傳 asyncio protocol 實作的可呼叫函式。

此方法將嘗試在背景建立連線。成功時,它將回傳一對 (transport, protocol)

底層操作的時間軸簡介如下:

  1. 建立連線並為其建立傳輸

  2. protocol_factory 在無引數的情況下被呼叫,並且預計回傳一個 協定 實例。

  3. 透過呼叫其 connection_made() 方法,將協定實例與傳輸連線在一起。

  4. 成功時回傳一個 (transport, protocol) 元組。

建立的傳輸是一個依賴實作的雙向串流。

其他引數:

  • 若有給定 ssl 且非 false,將建立 SSL/TLS 傳輸(預設建立普通 TCP 傳輸)。如果 sslssl.SSLContext 物件,則使用該情境來建立傳輸;如果 sslTrue,則使用 ssl.create_default_context() 回傳的預設情境。

  • server_hostname 設置或覆蓋目標伺服器憑證將匹配的主機名稱。僅在 ssl 不為 None 時傳遞。預設情況下,將使用 host 引數的值。如果 host 為空,則沒有預設值,必須傳遞 server_hostname 的值。若 server_hostname 為空字串,將停用主機名稱匹配(這是一個嚴重的安全風險,可能導致中間人攻擊)。

  • familyprotoflags 是可選的位址家族、協定和旗標,用於傳遞至 getaddrinfo() 進行 host 解析。若有給定這些應該都是相應 socket 模組常數的整數。

  • 若有給定,happy_eyeballs_delay 會啟用此連線的 Happy Eyeballs。它應該是一個浮點數,表示等待連線嘗試完成的秒數,然後在並行啟動下一次嘗試。這是 RFC 8305 中定義的「連線嘗試延遲」。RFC 建議的合理預設值為 0.25 秒(250 毫秒)。

  • interleave 控制主機名稱解析為多個 IP 位址時的地址重新排序。若為 0 或未指定,將不執行重排序,並按 getaddrinfo() 回傳的順序嘗試位址。如果指定正整數,則按地址家族交錯排列,給定的整數直譯為 RFC 8305 中定義的「首個地址家族計數」。如果未指定 happy_eyeballs_delay,則預設值為 0,如果指定則為 1

  • 若有給定 sock 則其應為已存在且已連線的 socket.socket 物件,可供傳輸使用。如果提供了 sock,則不應指定 hostportfamilyprotoflagshappy_eyeballs_delayinterleavelocal_addr 中的任何一項。

    備註

    引數 sock 將 socket 所有權轉移給所建立的傳輸 socket,請呼叫傳輸的 close() 方法。

  • 若有給定 local_addr 則其為一個 (local_host, local_port) 元組,用於在本地綁定 socket。將使用 getaddrinfo() 查找 local_hostlocal_port,方式類似於 hostport

  • ssl_handshake_timeout (對於 TLS 連線)是等待 TLS 交握的時間,以秒為單位,在那之前若未完成則會中斷連線。如果為 None (預設值),則會等待 60.0 秒。

  • ssl_shutdown_timeout 是等待 SSL 關閉完成以前中斷連線的時間,以秒為單位。如果為 None (預設值),則會等待 30.0 秒。

  • all_errors 決定在無法建立連線時會引發哪些例外。預設情況下,只會引發單一 Exception:如果只有一個例外或所有錯誤訊息相同,則引發第一個例外,否則引發包含所有錯誤訊息的單一 OSError。當 all_errorsTrue 時,將引發包含所有例外的 ExceptionGroup (即使只有一個例外)。

在 3.5 版的變更: 新增 ProactorEventLoop 中的 SSL/TLS 支援。

在 3.6 版的變更: 所有 TCP 連線都預設有 socket.TCP_NODELAY socket 選項。

在 3.7 版的變更: 增加 ssl_handshake_timeout 參數。

在 3.8 版的變更: 加入 happy_eyeballs_delayinterleave 參數。

Happy Eyeballs 演算法:雙協定堆疊主機 (Dual-Stack Hosts) 的成功。當伺服器的 IPv4 路徑和協定運作正常,但伺服器的 IPv6 路徑和協定不運作時,雙棧用戶端應用程式會比僅具 IPv4 的用戶端體驗到顯著的連線延遲。這是不希望的,因為這會導致雙棧用戶端的使用者體驗變差。本文件具體說明了減少此用戶可見延遲的演算法要求並提供了一種演算法。

更多資訊請見: https://datatracker.ietf.org/doc/html/rfc6555

在 3.11 版的變更: 增加 ssl_shutdown_timeout 參數。

在 3.12 版的變更: 已新增 all_errors

也參考

函式 open_connection() 是高階的替代 API。它回傳一對 (StreamReader, StreamWriter) 可直接在 async/await 程式碼中使用。

async loop.create_datagram_endpoint(protocol_factory, local_addr=None, remote_addr=None, *, family=0, proto=0, flags=