簡體   English   中英

Python asyncio 任務排序

[英]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回調列表並調用它們。 因此,對於任務的初始運行,它們按照創建的順序執行。

當任務awaitsyield一個未來時,它實際上取決於任務的_wakeup方法被放入隊列時該未來的性質。

另外,請注意,可以在創建任務之間注冊其他回調。 雖然如果TaskA是在 TaskB 之前創建的,那么TaskB的初始運行將在TaskA之前TaskB ,但仍然可能有其他回調在其間運行。

最后,上述行為也適用於asyncio附帶的默認Task類。 但是,可以指定自定義任務工廠並使用也可以更改此行為的替代任務實現。

(這是對 D-Rock 回答的跟進,太長了,無法發表評論。)

回調的執行順序在一些地方的 asyncio 文檔中得到保證。

loop.call_soon()文檔保證執行順序

回調按照它們注冊的順序被調用。 每個回調將被調用一次。

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM