簡體   English   中英

在另一個 asyncio.gather() 中使用嵌套的 asyncio.gather()

[英]Using nested asyncio.gather() inside another asyncio.gather()

我有一個具有各種方法的課程。 我在該類中有一個方法,例如:

 class MyClass:

    async def master_method(self):
      tasks = [self.sub_method() for _ in range(10)]
      results = await asyncio.gather(*tasks)

    async def sub_method(self):
      subtasks = [self.my_task() for _ in range(10)]
      results = await asyncio.gather(*subtasks)

    async def my_task(self):
      return "task done"  

所以這里的問題是:

  1. 在從另一個asyncio.gather()調用的協程中使用asyncio.gather()是否有任何問題、優點/缺點? 任何性能問題?

  2. asyncio循環是否以相同的優先級處理所有級別的所有任務? 這會給出相同的性能,如果我呼吁所有的協程從單一asyncio.gather() master_method

TLDR:使用gather而不是返回任務簡化了使用並使代碼更易於維護。 雖然gather有一些開銷,但對於任何實際應用來說都是可以忽略不計的。


為什么要gather

在退出協程之前gather子任務的要點是延遲協程的完成,直到其子任務完成。 封裝了實現,並確保協程作為一個單一的實體“做它的事”。
另一種方法是return子任務,並期望調用者將它們運行到完成。

為簡單起見,讓我們看一個單層——對應於中間的sub_method但有不同的變化。

async def child(i):
    await asyncio.sleep(0.2)  # some non-trivial payload
    print("child", i, "done")

async def encapsulated() -> None:
    await asyncio.sleep(0.1)  # some preparation work
    children = [child() for _ in range(10)]
    await asyncio.gather(*children)

async def task_children() -> 'List[asyncio.Task]':
    await asyncio.sleep(0.1)  # some preparation work
    children = [asyncio.create_task(child()) for _ in range(10)]
    return children

async def coro_children() -> 'List[Awaitable[None]]':
    await asyncio.sleep(0.1)  # some preparation work
    children = [child() for _ in range(10)]
    return children

所有encapsulatedtask_childrencoro_children以某種方式編碼存在子任務。 這允許調用者以可靠地“完成”實際目標的方式運行它們。 但是,每個變體的不同之處在於它自己做了多少以及調用者必須做多少:

  • encapsulated是“最重”的變體:所有子項都在Task中運行,並且有一個額外的gather 但是,調用者不會暴露於任何以下內容:
     await encapsulated()
    這保證了功能按預期工作,並且可以自由更改其實現。
  • task_children是中間變體:所有孩子都在Task中運行。 調用者可以決定是否以及如何等待完成:
     tasks = await task_children() await asyncio.gather(*tasks) # can add other tasks here as well
    這保證了功能按預期啟動 但是,它的完成依賴於調用者具有一些知識。
  • coro_children是“最輕的”變體:實際上沒有任何孩子在運行。 調用者負責整個生命周期:
     tasks = await coro_children() # children don't actually run yet! await asyncio.gather(*tasks) # can add other tasks here as well
    這完全依賴於調用者來啟動和等待子任務。

使用encapsulated模式是一個安全的默認設置——它確保協程“正常工作”。 值得注意的是,使用內部gather的協程仍然看起來像任何其他協程。

速度gather

gather實用程序 a) 確保其參數作為Task運行,並且 b) 提供一個Future ,一旦任務完成就會觸發。 由於gather通常是用來當一個人將運行參數作為Task小號反正有從這個沒有額外的開銷; 同樣,這些是常規Task並且與其他所有內容具有相同的性能/優先級特征¹。

唯一的開銷來自包裝Future 這負責記賬(確保參數是任務),然后只等待,即什么都不做。 在我的機器上, 測量開銷表明它平均花費的時間是運行 no-op Task兩倍。 對於任何現實世界的任務,這本身應該已經可以忽略不計。

此外, gather子任務的模式本質上意味着存在一個gather節點 因此, gather節點的數量通常遠低於任務數量。 例如,對於每個gather 10 個任務的情況,總共只需要 11 個gather即可處理總共 100 個任務。

master_method                                                  0

sub_method         0          1          2          3          4          5 ...

my_task       0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 ...

¹也就是說,沒有。 asyncio目前沒有Task優先級的概念。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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