如果我在一个顶级类中创建一系列asyncio
任务,那么所有这些任务基本上应该永远运行,就像这样:
self.event_loop.create_task(...)
self.event_loop.create_task(...)
self.event_loop.create_task(...)
...
self.event_loop.run_forever()
# Once we fall out of the event loop, collect all remaining tasks,
# cancel them, and terminate the asyncio event loop
tasks = asyncio.Task.all_tasks()
group = asyncio.gather(*tasks, return_exceptions=True)
group.cancel()
self.event_loop.run_until_complete(group)
self.event_loop.close()
上面的代码无法处理以下情况,我发现自己越来越需要这种情况,并且在Googling或asyncio
文档中也没有看到示例:
如果其中一个任务因异常而失败,则该异常不会得到处理-其他所有任务都将继续执行,但是一个任务只是默默地暂停(除了异常输出)。
所以,我该怎么办
self.event_loop.create_task(...)
吗?这似乎需要找到在事件循环中接收到异常的任务,将其删除,然后添加一个新任务-如何做到这一点对我来说还不清楚。答案 0 :(得分:4)
未捕获的异常附加到任务对象,可以通过Task.exception()
method从该对象检索。 self.event_loop.create_task(...)
调用返回任务对象,因此您需要收集这些对象以检查异常。
如果您希望在发生异常时重新安排任务的时间,则希望在新任务中执行此操作(因为您希望它在事件循环中运行),或使用包装程序协程会捕获异常,然后再次重新运行给定的协程。
后者可能看起来像:
import traceback
def rerun_on_exception(coro, *args, **kwargs):
while True:
try:
await coro(*args, **kwargs)
except asyncio.CancelledError:
# don't interfere with cancellations
raise
except Exception:
print("Caught exception")
traceback.print_exc()
然后在将协程作为任务进行调度时,将协程用上述协程包起来:
self.event_loop.create_task(rerun_on_exception(coroutine_uncalled, arg1value, ... kwarg1=value, ...)
例如每次出现异常时,都要传入创建协程的参数。
另一种选择是在单独的任务中使用asyncio.wait()
,以便您可以在循环运行时监视异常,并决定如何在那里处理异常,然后:
def exception_aware_scheduler(*task_definitions, loop=None):
if loop is None:
loop = asyncio.get_event_loop()
task_arguments = {
loop.create_task(coro(*args, **kwargs)): (coro, args, kwargs)
for coro, args, kwargs in task_definitions
}
while tasks:
done, pending = await asyncio.wait(
tasks.keys(), loop=loop, return_when=asyncio.FIRST_EXCEPTION
)
for task in done:
if task.exception() is not None:
print('Task exited with exception:')
task.print_stack()
print('Rescheduling the task\n')
coro, args, kwargs = tasks.pop(task)
tasks[loop.create_task(coro(*args, **kwargs))] = coro, args, kwargs
当计划中的任何一项任务由于异常而退出时,事件循环将再次asyncio.wait()
进行控制,但是直到发生这种情况之前,任务可能已被取消或仅完成了工作。当某个任务由于异常而退出时,您需要一种方法来再次创建相同的协程(具有相同的参数),因此需要上面的*args, **kwargs
设置。
您只计划exception_aware_scheduler()
,传入您要传递的任务:
tasks = (
(coro1, (), {}), # no arguments
(coro2, ('arg1', 'arg2'), {}),
# ...
)
loop.create_task(exception_aware_scheduler(*task_definitions, loop=loop))