#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
?