Исключение в python asyncio.Задача не вызывается до завершения основной задачи

#python #python-asyncio

#python #python-asyncio

Вопрос:

Вот код, я думал, что программа сразу вылетит из-за неперехваченного исключения. Однако он ждал 10 секунд, когда основная задача coro2 завершится.

 import asyncio

@asyncio.coroutine
def coro1():
    print("coro1 primed")
    yield
    raise Exception("abc")

@asyncio.coroutine
def coro2(loop):
    try:
        print("coro2 primed")
        ts = [asyncio.Task(coro1(),loop=loop) for _ in range(2)]
        res = yield from asyncio.sleep(10)
        print(res)
    except Exception as e:
        print(e)
        raise

loop= asyncio.get_event_loop()
loop.run_until_complete(coro2(loop))
 

Я думаю, что это серьезная проблема, потому что в более сложных программах это приводит к тому, что процесс застревает навсегда, вместо сбоя с информацией об исключении.

Кроме того, я установил точку останова в except блоке в исходном коде run_until_complete , но она не срабатывает. Меня интересует, какой фрагмент кода обработал это исключение в python asyncio.

Комментарии:

1. Пожалуйста, имейте в виду, что @coroutine оформленные генераторы, а также loop аргумент устарели и больше не нужны в текущих версиях python / asyncio. Используйте собственные async def сопрограммы и полагайтесь на то, что цикл передается неявно; используйте asyncio.get_event_loop() / asyncio.get_running_loop() только тогда, когда цикл необходим явно.

Ответ №1:

Во-первых, нет причин использовать сопрограммы на основе генератора в Python с синтаксисом async / await, доступным в течение многих лет, а coroutine декоратор теперь устарел и запланирован к удалению. Кроме того, вам не нужно передавать цикл событий в каждую сопрограмму, вы всегда можете использовать asyncio.get_event_loop() его для получения, когда вам это нужно. Но это не связано с вашим вопросом.

except Блок в coro2 не сработал, потому что исключение, вызванное в coro1 , не распространилось на coro2 . Это потому, что вы явно запускались coro1 как задача, которая выполняла ее в фоновом режиме и не ожидала ее. Вы всегда должны быть уверены, что ваши задачи ожидаются, и тогда исключения не останутся незамеченными; систематическое выполнение этого иногда называют структурированным параллелизмом.

Правильный способ написать выше было бы что-то вроде:

 async def coro1():
    print("coro1 primed")
    await asyncio.sleep(0)  # yield to the event loop
    raise Exception("abc")

async def coro2():
    try:
        print("coro2 primed")
        ts = [asyncio.create_task(coro1()) for _ in range(2)]
        await asyncio.sleep(10)
        # ensure we pick up results of the tasks that we've started
        for t in ts:
            await t
        print(res)
    except Exception as e:
        print(e)
        raise

asyncio.run(coro2())
 

Обратите внимание, что это будет выполняться sleep() до завершения и только затем распространять исключения, вызванные фоновыми задачами. Если вы хотите немедленно распространить, вы можете использовать asyncio.gather() , и в этом случае вам не придется беспокоиться о явном создании задач в первую очередь:

 async def coro2():
    try:
        print("coro2 primed")
        res, *ignored = await asyncio.gather(
            asyncio.sleep(10),
            *[(coro1()) for _ in range(2)]
        )
        print(res)
    except Exception as e:
        print(e)
        raise
 

Меня интересует, какой фрагмент кода обработал это исключение в python asyncio.

Исключение, вызванное сопрограммой, которое не обрабатывается, перехватывается asyncio и сохраняется в объекте задачи. Это позволяет вам ожидать выполнения задачи или (если вы знаете, что она завершена) получить ее результат с помощью result() метода, любой из которых распространит (повторно вызовет) исключение. Поскольку ваш код никогда не обращался к результату задачи, экземпляр исключения оставался забытым внутри объекта задачи. Python заходит так далеко, что замечает это и выводит предупреждение «Исключение задачи не было получено», когда объект задачи уничтожается вместе с трассировкой, но это предупреждение предоставляется на основе наилучших усилий, обычно приходит слишком поздно, и на него не следует полагаться.

Комментарии:

1. Привет, спасибо за ответ. Похоже res , что это не определено в первом примере. Вероятно, то, что вы имели в виду, было res.append(await t) вместо await t ?