[英]Python asyncio task ordering
我有一個關於 python 的asyncio
模塊中的事件循環如何管理未完成任務的問題。 考慮以下代碼:
import asyncio
@asyncio.coroutine
def a():
for i in range(0, 3):
print('a.' + str(i))
yield
@asyncio.coroutine
def b():
for i in range(0, 3):
print('b.' + str(i))
yield
@asyncio.coroutine
def c():
for i in range(0, 3):
print('c.' + str(i))
yield
tasks = [
asyncio.Task(a()),
asyncio.Task(b()),
asyncio.Task(c()),
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait([t1, t2, t3]))
運行它將打印:
a.0
b.0
c.0
a.1
b.1
c.1
a.2
b.2
c.2
請注意,它總是打印出 'a' 然后 'b' 然后 'c'。 我猜無論每個協程經過多少次迭代,它總是會按該順序打印。 所以你永遠不會看到類似的東西
b.100
c.100
a.100
來自 node.js 背景,這告訴我這里的事件循環在內部維護一個隊列,它用來決定接下來運行哪個任務。 它最初將a()
放在隊列的前面,然后是b()
,然后是c()
,因為這是傳遞給asyncio.wait()
的列表中任務的順序。 然后,每當它遇到一個 yield 語句時,它就會將該任務放在隊列的末尾。 我想在一個更現實的例子中,假設您正在執行異步 http 請求,它會在 http 響應返回后將a()
放回隊列的末尾。
我可以得到一個阿門嗎?
目前,您的示例不包含任何阻塞 I/O 代碼。 試試這個來模擬一些任務:
import asyncio
@asyncio.coroutine
def coro(tag, delay):
for i in range(1, 8):
print(tag, i)
yield from asyncio.sleep(delay)
loop = asyncio.get_event_loop()
print("---- await 0 seconds :-) --- ")
tasks = [
asyncio.Task(coro("A", 0)),
asyncio.Task(coro("B", 0)),
asyncio.Task(coro("C", 0)),
]
loop.run_until_complete(asyncio.wait(tasks))
print("---- simulate some blocking I/O --- ")
tasks = [
asyncio.Task(coro("A", 0.1)),
asyncio.Task(coro("B", 0.3)),
asyncio.Task(coro("C", 0.5)),
]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
如您所見,協程是按需要安排的,而不是按順序安排的。
免責聲明至少對於具有默認實現的 v3.9,這似乎是正確的。 然而,事件循環的內部工作不是公共接口,因此可能會隨着新版本的變化而改變。 此外,asyncio 允許替換BaseEventLoop
實現,這可能會改變其行為。
創建Task
對象時,它會調用loop.call_soon
將其_step
方法注冊為回調。 _step
方法實際上是通過調用send()
來調用協程並處理結果。
在BaseEventLoop
中, loop.call_soon
將_step
回調放在_ready
回調列表的末尾。 事件循環的每次運行,都會以 FIFO 順序迭代_ready
回調列表並調用它們。 因此,對於任務的初始運行,它們按照創建的順序執行。
當任務awaits
或yield
一個未來時,它實際上取決於任務的_wakeup
方法被放入隊列時該未來的性質。
另外,請注意,可以在創建任務之間注冊其他回調。 雖然如果TaskA
是在 TaskB 之前創建的,那么TaskB
的初始運行將在TaskA
之前TaskB
,但仍然可能有其他回調在其間運行。
最后,上述行為也適用於asyncio
附帶的默認Task
類。 但是,可以指定自定義任務工廠並使用也可以更改此行為的替代任務實現。
(這是對 D-Rock 回答的跟進,太長了,無法發表評論。)
回調的執行順序在一些地方的 asyncio 文檔中得到保證。
回調按照它們注冊的順序被調用。 每個回調將被調用一次。
Future.add_done_callback()
文檔指定回調是通過loop.call_soon()
安排的,因此有這個保證的 FIFO 順序。
並且asyncio.Task
被描述為asyncio.Future
的子類,因此對於add_done_callback()
具有相同的行為。
所以我認為依賴異步回調的 FIFO 排序是非常安全的,至少在使用 vanilla asyncio 時是這樣。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.