如何在python asyncio后台任务中引发异常

nx7onnlm  于 2023-03-20  发布在  Python
关注(0)|答案(2)|浏览(357)

问题

我有几个连续运行的任务,但其中一个偶尔需要重新启动,所以这个任务在后台运行。如何立即引发来自这个后台任务的异常?在下面的示例中,直到下一次尝试重新启动时才会引发异常,这在真实的应用程序中可能非常罕见,因此可能长时间不被注意。

示例

https://replit.com/@PatrickPei/how-to-raise-exceptions-in-python-asyncio-background-task
此示例运行3个任务:

  1. foo,不引发异常
  2. bar,在6次迭代后引发异常
  3. on_interval,每5秒重新启动一次bar
import asyncio

task = None
i = 0

async def foo():
    while True:
        print("foo")
        await asyncio.sleep(1)

async def bar():
    while True:
        global i
        i += 1
        if i > 4:
            raise ValueError()

        print("bar", i)
        await asyncio.sleep(1)

async def on_interval(n):
    while True:
        await asyncio.sleep(n)

        # Cancel task.
        global task
        print("Canceling bar")
        task.cancel()
        try:
            await task
        except asyncio.CancelledError:
            pass

        # Restart task.
        print("Restarting bar")
        task = asyncio.create_task(bar())

async def main():
    # Start background task.
    print("Starting bar")
    global task
    task = asyncio.create_task(bar())

    # Start other tasks.
    await asyncio.gather(
        foo(),
        on_interval(3),
    )

if __name__ == "__main__":
    asyncio.run(main())

输出

bar迭代4次并引发一个异常,直到下一次重新启动才捕获该异常,如bar 4之后的3次foo迭代所示。当两次重新启动之间有大量时间时,这是一个问题,因为异常长时间未被注意到。

Starting bar
bar 1
foo
bar 2
foo
bar 3
foo
Canceling bar
Restarting bar
bar 4
foo
foo
foo
Canceling bar
Traceback (most recent call last):
  File "~/example.py", line 60, in <module>
    asyncio.run(main())
  File "~/.pyenv/versions/3.10.5/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "~/.pyenv/versions/3.10.5/lib/python3.10/asyncio/base_events.py", line 646, in run_until_complete
    return future.result()
  File "~/example.py", line 53, in main
    await asyncio.gather(
  File "~/example.py", line 37, in on_interval
    await task
  File "~/example.py", line 22, in bar
    raise ValueError()
ValueError

次尝试

  • 启动了另一个任务来检查asyncio.Task.exception,但这很麻烦,因为每个后台任务都需要另一个忙碌循环来帮助引发其异常。
  • 已尝试asyncio.Task.add_done_callback,但由于后台任务仍等待下次重新启动,因此它仅记录错误,而不会停止其它任务foo
carvr3hs

carvr3hs1#

在python3.11中,使用异步上下文管理器async withasyncio.TaskGroup()可以简单地解决这个问题。

import asyncio

i = 0

async def foo():
    while True:
        print("foo")
        await asyncio.sleep(1)

async def bar():
    while True:
        global i
        i += 1
        if i > 14:
            raise ValueError()

        print("bar", i)
        await asyncio.sleep(1)

async def on_interval(n):
    while True:
        async with asyncio.TaskGroup() as tg1:
            print("Restarting bar")
            task2 = tg1.create_task(bar())

            await asyncio.sleep(n)

            print("Canceling bar")
            task2.cancel()
         

async def main():
    async with asyncio.TaskGroup() as tg:
        task1 = tg.create_task(foo())
        task3 = tg.create_task(on_interval(3))

asyncio.run(main())

-------------------------

foo
Restarting bar
bar 1
foo
bar 2
foo
bar 3
Canceling bar
foo
Restarting bar
bar 4
  + Exception Group Traceback (most recent call last):
  |   File "C:\Users\ф\PycharmProjects\tkinter\rrr.py", line 42, in <module>
  |     asyncio.run(main())
  |   File "C:\Users\ф\AppData\Local\Programs\Python\Python311\Lib\asyncio\runners.py", line 190, in run
  |     return runner.run(main)
  |            ^^^^^^^^^^^^^^^^
  |   File "C:\Users\ф\AppData\Local\Programs\Python\Python311\Lib\asyncio\runners.py", line 118, in run
  |     return self._loop.run_until_complete(task)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File "C:\Users\ф\AppData\Local\Programs\Python\Python311\Lib\asyncio\base_events.py", line 650, in run_until_complete
  |     return future.result()
  |            ^^^^^^^^^^^^^^^
  |   File "C:\Users\ф\PycharmProjects\tkinter\rrr.py", line 37, in main
  |     async with asyncio.TaskGroup() as tg:
  |   File "C:\Users\ф\AppData\Local\Programs\Python\Python311\Lib\asyncio\taskgroups.py", line 135, in __aexit__
  |     raise me from None
  | ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
  +-+---------------- 1 ----------------
    | Exception Group Traceback (most recent call last):
    |   File "C:\Users\ф\PycharmProjects\tkinter\rrr.py", line 25, in on_interval
    |     async with asyncio.TaskGroup() as tg1:
    |   File "C:\Users\ф\AppData\Local\Programs\Python\Python311\Lib\asyncio\taskgroups.py", line 135, in __aexit__
    |     raise me from None
    | ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
    +-+---------------- 1 ----------------
      | Traceback (most recent call last):
      |   File "C:\Users\ф\PycharmProjects\tkinter\rrr.py", line 17, in bar
      |     raise ValueError()
      | ValueError
      +------------------------------------
foo

Process finished with exit code 1
slhcrj9b

slhcrj9b2#

将可能生成异常代码 Package 在try catch块中。

import asyncio

async def background_task():
    try:
        # code that could potentially raise an exception
    except Exception as e:
        # handle the exception as desired
        raise e

async def main():
    # start the background task
    task = asyncio.create_task(background_task())

    # wait for the background task to complete
    await task

if __name__ == "__main__":
    asyncio.run(main())

相关问题