我有两个任务。当一个任务引发错误时,我希望将它们都重新启动。 以下是捕获一个任务传播的异常并重新启动两个任务的收集的适当方法吗?
import asyncio
async def foo():
while True:
await asyncio.sleep(1)
print("foo")
async def bar():
for _ in range(3):
await asyncio.sleep(1)
print("bar")
raise ValueError
async def main():
while True:
footask = asyncio.create_task(foo())
bartask = asyncio.create_task(bar())
bothtasks = asyncio.gather(footask, bartask)
try:
await bothtasks
except ValueError:
print("caught ValueError")
try:
footask.cancel()
except asyncio.CancelledError:
pass
asyncio.run(main())
基本上,asyncio
在一个任务引发错误时不会有意取消集合中的其他任务。因此,由于我想不出更好的办法,因此我手动用task.cancel()
取消了其他任务,自己处理了asyncio.CancelledError
。
我只是不相信这是该api的预期用途,感谢您的见解。
编辑:-
在asyncio-3.7文档中,它读取
如果取消了 gather(),所有已提交的等待(尚未完成)也将被取消。
但是当我用footask.cancel()
替换bothtasks.cancel()
时观察到的行为是,对于while循环的每次迭代,都需要等待另外的foo
,即出现foo
不要通过取消聚集来取消。输出看起来像这样:
foo
bar
foo
bar
foo
bar
caught ValueError
foo
foo
bar
foo
foo
bar
foo
foo
bar
caught ValueError
foo
foo
foo
bar
foo
foo
foo
bar
foo
foo
foo
bar
caught ValueError
...
答案 0 :(得分:1)
发生异常时,footask
不会取消,因为您可以阅读in doc:
如果return_exceptions为False(默认值),则第一个引发的异常为 立即传播到在collect()上等待的任务。 其他 aws序列中的等待项不会被取消,并且会继续 运行。
所以我们应该手动取消footask
和await it was cancelled:
async def main():
while True:
footask = asyncio.create_task(foo())
bartask = asyncio.create_task(bar())
bothtasks = asyncio.gather(footask, bartask)
try:
await bothtasks
except ValueError:
print("caught ValueError")
footask.cancel() # cancel just mark task to be cancelled
try:
await footask # await actually been cancelled
except asyncio.CancelledError:
pass
更新:
我写的advanced_gather
的行为类似于gather
,但有一个额外的kawrg cancel_on_exception
可以取消其中一个任务中的所有异常任务。完整代码:
import asyncio
async def advanced_gather(
*aws,
loop=None,
return_exceptions=False,
cancel_on_exception=False
):
tasks = [
asyncio.ensure_future(aw, loop=loop)
for aw
in aws
]
try:
return await asyncio.gather(
*tasks,
loop=loop,
return_exceptions=return_exceptions
)
except Exception:
if cancel_on_exception:
for task in tasks:
if not task.done():
task.cancel()
await asyncio.gather(
*tasks,
loop=loop,
return_exceptions=True
)
raise
async def foo():
while True:
await asyncio.sleep(1)
print("foo")
async def bar():
for _ in range(3):
await asyncio.sleep(1)
print("bar")
raise ValueError
async def main():
while True:
try:
await advanced_gather(
foo(),
bar(),
cancel_on_exception=True
)
except ValueError:
print("caught ValueError")
asyncio.run(main())
可能发生的不同情况:
import asyncio
from contextlib import asynccontextmanager, suppress
async def test(_id, raise_exc=False):
if raise_exc:
print(f'we raise RuntimeError inside {_id}')
raise RuntimeError('!')
try:
await asyncio.sleep(0.2)
except asyncio.CancelledError:
print(f'cancelledError was raised inside {_id}')
raise
else:
print(f'everything calm inside {_id}')
@asynccontextmanager
async def prepared_stuff(foo_exc=False):
foo = asyncio.create_task(test('foo', raise_exc=foo_exc))
bar = asyncio.create_task(test('bar'))
gather = asyncio.gather(
foo,
bar
)
await asyncio.sleep(0) # make sure everything started
yield (foo, bar, gather)
try:
await gather
except Exception as exc:
print(f'gather raised {type(exc)}')
finally:
# make sure both tasks finished:
await asyncio.gather(
foo,
bar,
return_exceptions=True
)
print('')
# ----------------------------------------------
async def everyting_calm():
async with prepared_stuff() as (foo, bar, gather):
print('everyting_calm:')
async def foo_raises_exception():
async with prepared_stuff(foo_exc=True) as (foo, bar, gather):
print('foo_raises_exception:')
async def foo_cancelled():
async with prepared_stuff() as (foo, bar, gather):
print('foo_cancelled:')
foo.cancel()
async def gather_cancelled():
async with prepared_stuff() as (foo, bar, gather):
print('gather_cancelled:')
gather.cancel()
async def main():
await everyting_calm()
await foo_raises_exception()
await foo_cancelled()
await gather_cancelled()
asyncio.run(main())
答案 1 :(得分:1)
确保任务已完成取消的标准习惯用法是在取消之后添加UIView
。例如:
gather(*tasks, return_exceptions=True)
请注意,您可能想对所有 all 异常(不仅仅是async def main():
while True:
footask = asyncio.create_task(foo())
bartask = asyncio.create_task(bar())
tasks = (footask, bartask) # or a list comprehension, etc.
try:
await asyncio.gather(*tasks)
except ValueError:
print("caught ValueError")
for t in tasks:
t.cancel()
await asyncio.gather(*tasks, return_exceptions=True)
)执行此操作,因为否则,以非ValueError
异常完成的任务仍会导致其他任务继续运行。