简体   繁体   English

如果coroutine使用asyncio引发异常,如何关闭循环和打印错误?

[英]How to shutdown the loop and print error if coroutine raised an exception with asyncio?

Suppose I have a few coroutines running in a loop. 假设我有几个协程在循环中运行。 How to make so that if some of them failed with exception the whole program would fail with this exception? 如何使得如果其中一些失败而异常整个程序会因此异常而失败? Because right now asyncio doesn't even prints the error messages from coroutines unless I use logging level "DEBUG". 因为现在asyncio甚至不打印来自协程的错误消息,除非我使用日志级别“DEBUG”。

from asyncio import get_event_loop, sleep


async def c(sleep_time=2, fail=False):
    print('c', sleep_time, fail)
    if fail:
        raise Exception('fail')
    while True:
        print('doing stuff')
        await sleep(sleep_time)



loop = get_event_loop()
loop.create_task(c(sleep_time=10, fail=False))
loop.create_task(c(fail=True))
loop.run_forever()

A graceful way is using error handling api. 一种优雅的方式是使用错误处理api。

https://docs.python.org/3/library/asyncio-eventloop.html#error-handling-api https://docs.python.org/3/library/asyncio-eventloop.html#error-handling-api

Example: 例:

import asyncio


async def run_division(a, b):
    await asyncio.sleep(2)
    return a / b


def custom_exception_handler(loop, context):
    # first, handle with default handler
    loop.default_exception_handler(context)

    exception = context.get('exception')
    if isinstance(exception, ZeroDivisionError):
        print(context)
        loop.stop()

loop = asyncio.get_event_loop()

# Set custom handler
loop.set_exception_handler(custom_exception_handler)
loop.create_task(run_division(1, 0))
loop.run_forever()

Here are some notes that you might want use to craft your solution: 以下是您可能想要用来制作解决方案的一些注意事项:

The easiest way to retrieve a couroutine's exception (or result!) is to await for it. 检索couroutine异常(或结果!)的最简单方法是await它。 asyncio.gather() will create tasks from coroutines and wrap all of them in one encompassing task that will fail if one of the subtasks fails: asyncio.gather()将从协同程序创建任务,并将所有这些任务包装在一个包含任务中,如果其中一个子任务失败,它将失败:

import asyncio

import random


async def coro(n):
    print("Start", n)
    await asyncio.sleep(random.uniform(0.2, 0.5))
    if n % 4 == 0:
        raise Exception('fail ({})'.format(n))
    return "OK: {}".format(n)


async def main():
    tasks = [coro(i) for i in range(10)]
    await asyncio.gather(*tasks)
    print("done")

loop = asyncio.get_event_loop()
try:
    asyncio.ensure_future(main())
    loop.run_forever()
finally:
    loop.close()

This however does not shutdown the loop. 然而,这不会关闭循环。 To stop a running loop, use loop.stop() . 要停止正在运行的循环,请使用loop.stop() Use this instead: 请改用:

async def main():
    tasks = [coro(i) for i in range(10)]
    try:
        await asyncio.gather(*tasks)
    except Exception as e:
        loop.stop()
        raise
    print("done")

Stopping the loop while some long-running coroutines are running is probably not what you want. 在一些长时间运行的协同程序运行时停止循环可能不是你想要的。 You might want to first signal some your coroutines to shut down using an event: 您可能希望首先通过事件向您的协程发出信号以关闭:

import asyncio

import random


async def repeat(n):
    print("start", n)
    while not shutting_down.is_set():
        print("repeat", n)
        await asyncio.sleep(random.uniform(1, 3))
    print("done", n)


async def main():
    print("waiting 6 seconds..")
    await asyncio.sleep(6)
    print("shutting down")
    shutting_down.set()  # not a coroutine!
    print("waiting")
    await asyncio.wait(long_running)
    print("done")
    loop.stop()

loop = asyncio.get_event_loop()
shutting_down = asyncio.Event(loop=loop)
long_running = [loop.create_task(repeat(i + 1))  for i in range(5)]
try:
    asyncio.ensure_future(main())
    loop.run_forever()
finally:
    loop.close()

If you don't want to await for your tasks, you might want to use an asyncio.Event (or asyncio.Queue ) to signal a global error handler to stop the loop: 如果您不想await任务,可能需要使用asyncio.Event (或asyncio.Queue )来通知全局错误处理程序以停止循环:

import asyncio


async def fail():
    try:
        print("doing stuff...")
        await asyncio.sleep(0.2)
        print("doing stuff...")
        await asyncio.sleep(0.2)
        print("doing stuff...")
        raise Exception('fail')
    except Exception as e:
        error_event.payload = e
        error_event.set()
        raise  # optional


async def error_handler():
    await error_event.wait()
    e = error_event.payload
    print("Got:", e)
    raise e


loop = asyncio.get_event_loop()
error_event = asyncio.Event()
try:
    loop.create_task(fail())
    loop.run_until_complete(error_handler())
finally:
    loop.close()

(Used here with run_until_complete() for simplicity, but can be used with loop.stop() as well) (为简单起见,此处使用run_until_complete() ,但也可以与loop.stop()一起使用)

Okay, I've found the solution that doesn't require rewriting any existing code. 好的,我发现解决方案不需要重写任何现有代码。 It may seem hacky, but I think I like it. 它可能看起来很丑陋,但我想我喜欢它。

Since I already catch KeyboardInterrupt like so. 因为我已经像这样捕获了KeyboardInterrupt

def main(task=None):
    task = task or start()
    loop = get_event_loop()
    try:
        task = loop.create_task(task)
        future = ensure_future(task)
        loop.run_until_complete(task)
    except KeyboardInterrupt:
        print('\nperforming cleanup...')
        task.cancel()
        loop.run_until_complete(future)
        loop.close()
        sys.exit()

How about sending KeyboardInterrupt to itself from a coroutine? 如何从协程发送KeyboardInterrupt到自己? I thought that this would hang the application, because os.kill would wait for application to close and because the application it would wait is the same application it would make kind of a deadlock, but thankfully I was wrong. 我认为这会挂起应用程序,因为os.kill会等待应用程序关闭,因为它等待的应用程序是相同的应用程序,它会造成一种死锁,但幸好我错了。 And this code actually works and prints clean up before exiting. 这段代码实际上可以工作并在退出之前打印clean up

async def c(sleep_time=2, fail=False):
    print('c', sleep_time, fail)
    if fail:
        await sleep(sleep_time)
        os.kill(os.getpid(), signal.SIGINT)
    while True:
        print('doing stuff')
        await sleep(sleep_time)



loop = get_event_loop()
loop.create_task(c(sleep_time=10, fail=False))
loop.create_task(c(fail=True))
try:
    loop.run_forever()
except KeyboardInterrupt:
    print('clean up')
    loop.close()
    sys.exit()

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

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