[英]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
這個版本按預期工作!
在這方面,我有下一個問題:
您說 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
其他問題的答案:
async def test():
return 1
f = test()
f = None # after that you will get that error
第一個版本不起作用,因為in_sequence
沒有捕獲可以在await task
上引發的異常。 第二個有效,因為create_task
創建了一個運行協程的類似未來的任務object。 object 不會返回/傳播包裝協程的結果。 當您await
object 時,它會掛起,直到有結果或異常集或直到它被取消。
似乎沒有。
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.