簡體   English   中英

asyncio.gather 的順序版本

[英]Sequential version of asyncio.gather

我嘗試創建一個類似於 asyncio.gather 的方法,但它將按順序而不是異步執行任務列表:

async def in_sequence(*tasks):
    """Executes tasks in sequence"""
    for task in tasks:
        await task

接下來這個方法應該像這樣使用:

async def some_work(work_name):
    """Do some work"""
    print(f"Start {work_name}")
    await asyncio.sleep(1)
    if raise_exception:
        raise RuntimeError(f"{work_name} raise an exception")
    print(f"Finish {work_name}")

async def main():
    try:
        await asyncio.gather(
            some_work("work1"),         # work1, work2, in_sequence and work5 executed in concurrently
            some_work("work2"),
            in_sequence(
                some_work("work3"),     # work3 and work4 executed in sequence
                some_work("work4")
            ),
            some_work("work5"),


    except RuntimeError as error:
        print(error)                    # raise an exception at any point to terminate

在我嘗試在 some_work 中拋出異常之前一切正常:

async def main():
    try:
        await asyncio.gather(
            some_work("work1"),
            some_work("work2"),
            in_sequence(
                some_work("work3", raise_exception=True),       # raise an exception here
                some_work("work4")
            ),
            some_work("work5"),


    except RuntimeError as error:
        print(error)

緊接着,我收到以下錯誤消息:

RuntimeWarning: coroutine 'some_work' was never awaited

我閱讀了文檔並繼續進行實驗:

async def in_sequence(*tasks):
    """Executes tasks in sequence"""
    _tasks = []
    for task in tasks:
        _tasks.append(asyncio.create_task(task))

    for _task in _tasks:
        await _task

這個版本按預期工作!

在這方面,我有下一個問題:

  1. 為什么第二個版本有效而第一個無效?
  2. asyncio 是否已經擁有按順序執行任務列表的工具?
  3. 我選擇了正確的實施方法還是有更好的選擇?

您說 in_sequence 的版本有效(使用 asyncio.create_task),但我認為它沒有。 來自文檔

將 coro 協程包裝成一個 Task 並安排其執行。 返回任務 object。

它似乎並行運行協程,但您需要按順序運行它們。

所以實驗並找到了兩種方法來解決這個問題

使用您原來的 in_sequence function 並添加此代碼,隱藏該錯誤:

import warnings
warnings.filterwarnings(
    'ignore',
    message=r'^coroutine .* was never awaited$',
    category=RuntimeWarning
)

修復 in_sequence function,像這樣:

async def in_sequence(*tasks):
    for index, task in enumerate(tasks):
        try:
            await task
        except Exception as e:
            for task in tasks[index + 1:]:
                task.close()
            raise e

其他問題的答案:

  1. 當您在協程上沒有鏈接時,該警告由 C++ 代碼觸發。 只需簡單的代碼就可以向您展示這個想法(在終端中):

async def test():
    return 1

f = test()
f = None # after that you will get that error
  1. 我不知道
  2. 看上面
  1. 第一個版本不起作用,因為in_sequence沒有捕獲可以在await task上引發的異常。 第二個有效,因為create_task創建了一個運行協程的類似未來的任務object。 object 不會返回/傳播包裝協程的結果。 當您await object 時,它會掛起,直到有結果異常或直到它被取消

  2. 似乎沒有。

  3. 第二個版本會並發執行傳遞過來的協程,所以是不正確的實現。 如果你真的想使用一些in_sequence function 你可以:
    • 不知何故延遲了協程的創建。
    • async function 中分組順序執行

例如:

async def in_sequence(*fn_and_args):
    for fn, args, kwargs in fn_and_args:
        await fn(*args, **kwargs)  # create a coro and await it in place

in_sequence(
    (some_work, ("work3",), {'raise_exception': True}),
    (some_work, ("work4",), {}),
)
async def in_sequence():
    await some_work("work3", raise_exception=True)
    await some_work("work4")

這個版本按預期工作!

第二個版本的問題在於它實際上並沒有順序運行協程,而是並行運行它們。 這是因為asyncio.create_task()調度協程與當前協程並行運行。 因此,當您在循環中等待任務時,實際上是在等待第一個任務時允許所有任務運行。 盡管看起來,整個循環只會運行最長的任務。 (有關詳細信息,請參閱此處。)

您的第一個版本顯示的警告旨在防止您意外創建一個您從不等待的協程,例如只編寫asyncio.sleep(1)而不是await asyncio.sleep(1) 就 asyncio 而言, main正在實例化協程對象並將它們傳遞給“忘記”等待其中一些對象的in_sequence

抑制警告消息的一種方法是允許協程旋轉,但立即取消它。 例如:

async def in_sequence(*coros):
    remaining = iter(coros)
    for coro in remaining:
        try:
            await coro
        except Exception:
            for c in remaining:
                asyncio.create_task(c).cancel()
            raise

請注意,以下划線開頭的變量名稱標記未使用的變量,因此如果您確實使用它們,則不應命名變量。

從 user4815162342 和 Anton Pomieshchenko 的解決方案中汲取靈感,我想出了它的這個變體:

async def in_sequence(*storm):
    twister = iter(storm)
    for task in twister:
        task = task() # if it's a regular function, it's done here.
        if inspect.isawaitable(task):
            try:
                await task # if it's also awaitable, await it
            except BaseException as e:
                task.throw(e) # if an error occurs, throw it into the coroutine
            finally:
                task.close() # to ensure coroutine closer

    assert not any(twister) # optionally verify that the iterator is now empty

通過這種方式,您可以將常規函數與協程與此in_sequence結合起來。 但一定要這樣稱呼它:

await in_sequence(*[b.despawn, b.release])

請注意缺少() ( __call__() ),因為否則常規的 function 將立即被調用,並且協程將因從未等待而引發RuntimeWarning b.despawn是一個協程,而b.release不是我的例子)

您還可以在調用task()之前對callable(task)進行額外檢查,但這取決於您。

暫無
暫無

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

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