Асинхронная обработка исключений в Python

у меня есть следующий код, используя asyncio и aiohttp для выполнения асинхронных HTTP-запросов.

import sys
import asyncio
import aiohttp

@asyncio.coroutine
def get(url):
    try:
        print('GET %s' % url)
        resp = yield from aiohttp.request('GET', url)
    except Exception as e:
        raise Exception("%s has error '%s'" % (url, e))
    else:
        if resp.status >= 400:
            raise Exception("%s has error '%s: %s'" % (url, resp.status, resp.reason))

    return (yield from resp.text())

@asyncio.coroutine
def fill_data(run):
    url = 'http://www.google.com/%s' % run['name']
    run['data'] = yield from get(url)

def get_runs():
    runs = [ {'name': 'one'}, {'name': 'two'} ]
    loop = asyncio.get_event_loop()
    task = asyncio.wait([fill_data(r) for r in runs])
    loop.run_until_complete(task)   
    return runs

try:
    get_runs()
except Exception as e:
    print(repr(e))
    sys.exit(1)

по какой-то причине исключения, поднятые внутри

3 ответов


asyncio.wait фактически не потребляет Futures передано ему, он просто ждет их завершения, а затем возвращает Future объекты:

coroutine asyncio.wait(futures, *, loop=None, timeout=None, return_when=ALL_COMPLETED)

дождитесь объектов Futures и coroutine задается последовательность фьючерсов для завершения. Coroutines будут обернуты в задачах. Возвращает два набора Future: (сделано, в ожидании).

пока вы на самом деле yield from элементы в done список, они останутся невостребованные. Поскольку ваша программа выходит, не потребляя фьючерсы, вы видите сообщения "исключение никогда не было получено".

для вашего случая использования, вероятно, имеет смысл использовать asyncio.gather, который фактически будет потреблять каждый Future, а затем верните один Future что объединяет все их результаты (или вызывает "первый"!--12--> брошенный будущим во вход список.)

def get_runs():
    runs = [ {'name': 'one'}, {'name': 'two'} ]
    loop = asyncio.get_event_loop()
    tasks = asyncio.gather(*[fill_data(r) for r in runs])
    loop.run_until_complete(tasks)
    return runs

выход:

GET http://www.google.com/two
GET http://www.google.com/one
Exception("http://www.google.com/one has error '404: Not Found'",)

отметим, что asyncio.gather фактически позволяет настроить его поведение, когда один из фьючерсов вызывает исключение; поведение по умолчанию, чтобы поднять первое исключение он попадает, но он также может просто вернуть каждый объект исключения в списке вывода:

asyncio.gather(*coros_or_futures, loop=None, return_exceptions=False)

возвращает будущие результаты агрегирования из заданных объектов coroutine или будущее.

все фьючерсы должны иметь один и тот же цикл событий. Если все задачи выполнены успешно, результат возвращенного будущего-это список результатов (в порядок исходной последовательности, не обязательно порядок результаты заезда). если return_exceptions is True исключения в задачи рассматриваются так же, как и успешные результаты, и собираются в список результатов; в противном случае первое вызванное исключение будет немедленно распространяется на возвращаемые будущее.


для отладки или" обработки " исключений в обратный звонок:

Coroutine, которые возвращают некоторый результат или вызывают исключения:

@asyncio.coroutine
def async_something_entry_point(self):
    try:
        return self.real_stuff_which_throw_exceptions()
    except:
        raise Exception(some_identifier_here + ' ' + traceback.format_exc())

и обратный вызов:

def callback(self, future: asyncio.Future):
    exc = future.exception()
    if exc:
        # Handle wonderful empty TimeoutError exception
        if type(exc) == TimeoutError:
            self.logger('<Some id here> callback exception TimeoutError')
        else:
            self.logger("<Some id here> callback exception " + str(exc))

    # store your result where you want
    self.result.append(
        future.result()
    )

asyncio предоставляет API для индивидуальной обработки ошибок, пожалуйста, обратитесь к документу https://docs.python.org/3/library/asyncio-eventloop.html#error-handling-api