简体   繁体   English

管理递归 Python 异步任务的正确方法

[英]Proper way of managing recursive Python async tasks

My understanding of async tasks is that they can be created and the returned task object itself can just be discarded because the task will automatically go on the loop, and then asyncio.all_tasks() can be called later to join them.我对异步任务的理解是可以创建它们,返回的任务 object 本身可以直接丢弃,因为任务会在循环上自动 go ,然后可以稍后调用asyncio.all_tasks()加入它们。 However, hitting all_tasks() once won't account for any tasks created from tasks, and, as a result, an exception will get raised but not propagated.但是,点击all_tasks()一次不会考虑从任务创建的任何任务,因此,将引发异常但不会传播。 Even after the task that dispatches its own task is executed, asyncio.all_tasks() still doesn't seem to see it.即使在执行了分派自己任务的任务之后, asyncio.all_tasks()似乎仍然看不到它。

So, do we actually need proper task accounting and to make sure to gather/run on all created tasks?那么,我们真的需要适当的任务统计并确保收集/运行所有创建的任务吗? It seems like this is partially managed, but the important stuff is not.看起来这是部分管理的,但重要的东西不是。

Example code:示例代码:

import asyncio

async def func2():
    print("Second function")

    raise Exception("Inner exception")

async def func1(loop):
    print("First function")

    loop.create_task(func2())

loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)

awaitable = func1(loop)
loop.create_task(awaitable)

reported_tasks = asyncio.all_tasks(loop=loop)

awaitable = asyncio.gather(*reported_tasks)
loop.run_until_complete(awaitable)

I'm creating the loop myself because my scenario is a non-main thread (so the loop won't be implicitly created) and I don't want there to be some nuance due to this that confuses things due to being left unspoken.我自己创建了循环,因为我的场景是一个非主线程(因此不会隐式创建循环),并且我不希望有一些细微差别,因为这会因为未说出口而混淆事物。

func1() and func2() execute, but I'll merely just get warned about not materializing the exception in func2() unless I manually collect all of the tasks, enumerate them, and check their exception flags. func1()func2()执行,但我只会被警告不要在func2()中实现异常,除非我手动收集所有任务,枚举它们并检查它们的异常标志。

On the other hand, if I start adding all of the tags to a list, do I grab that list, get the length, shift that number of items off the front of the list, wait on those to be done, check them for exceptions, and loop until I determine that all tasks have completed?另一方面,如果我开始将所有标签添加到列表中,我是否抓取该列表,获取长度,将该数量的项目移出列表的前面,等待那些完成,检查它们是否有异常, 并循环直到我确定所有任务都已完成? This will potentially include some long-running tasks as well as some failed ones, so maybe I need to figure for a timeout in order to make sure those exceptions get processed seen after being raised?这可能包括一些长时间运行的任务以及一些失败的任务,所以也许我需要计算一个超时时间,以确保这些异常在被引发后得到处理? Is there a more elegant solution?有没有更优雅的解决方案?

I looked, but the documentation seems mostly about the API rather than the use-cases/patterns and all of the searches I ran still left these questions unanswered.我看了看,但文档似乎主要是关于 API 而不是用例/模式,我运行的所有搜索仍然没有回答这些问题。

The documentation does note that you should retain the created tasks and implies that this is for the reason of reference-counting, but there's no reference to the topics above:该文档确实指出您应该保留创建的任务,并暗示这是出于引用计数的原因,但没有提及上述主题:

Important重要的
Save a reference to the result of this function, to avoid a task disappearing mid execution.保存对此 function 结果的引用,以避免任务在执行过程中消失。

The problem with your snippet seems to be only that you call all_tasks before the body of func1 is run, and therefore, before the task that will run func2 is ever created.您的代码片段的问题似乎只是您在运行func1的主体之前调用all_tasks ,因此,在创建将运行func2的任务之前。

I typed some stuff on the interactive interpreter, without all the boilerplate you added there, and all_tasks seems to be "seeing" the grand-daughter task, with no problems, and the exception is also raised in the awaiting code:我在交互式解释器上输入了一些东西,没有您在其中添加的所有样板,并且all_tasks似乎正在“看到”孙女任务,没有问题,并且在等待代码中也引发了异常:


In [1]: import asyncio

In [2]: async def func2():
   ...:     print("second function")
   ...:     1 / 0  # raises
   ...: 

In [3]: async def func1():
   ...:     print("first function")
   ...:     asyncio.create_task(func2())
   ...: 

In [4]: async def main():
   ...:     await func1()
   ...:     z = asyncio.all_tasks()
   ...:     print(z)
   ...: 

In [5]: asyncio.run(main())
first function
{<Task pending name='Task-1148' coro=<main() running at <ipython-input-4-489680e1b4c1>:4> cb=[_run_until_complete_cb() at /home/gwidion/.pyenv/versions/3.10-dev/lib/python3.10/asyncio/base_events.py:184]>, <Task pending name='Task-1149' coro=<func2() running at <ipython-input-2-555de5f64f41>:1>>}
second function
Task exception was never retrieved
future: <Task finished name='Task-1149' coro=<func2() done, defined at <ipython-input-2-555de5f64f41>:1> exception=ZeroDivisionError('division by zero')>
Traceback (most recent call last):
  File "<ipython-input-2-555de5f64f41>", line 3, in func2
    1 / 0  # raises
ZeroDivisionError: division by zero

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM