Исключение при упаковке asyncio.gather - PullRequest
1 голос
/ 04 апреля 2020

Я экспериментировал с asyncio.gather следующим образом:

async def some_work(work_name, timeout, raise_exception=False):
    """Do some work"""
    print(f"Start {work_name}")
    await asyncio.sleep(timeout)
    if raise_exception:
        raise RuntimeError(f"{work_name} raise an exception")
    print(f"Finish {work_name}")


async def main():
    try:
        await asyncio.gather(
            some_work("work1", 3),
            some_work("work2", 1),
            some_work("work3", 2),
            asyncio.gather(
                some_work("work4", 3),
                some_work("work5", 1, raise_exception=True),
                some_work("work6", 2)
            )
        )

    except RuntimeError as error:
        print(error)


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

В какой-то момент я решил создать оболочку для asyncio.gather, например так:

# Yes I know, concurrently really
def in_parallel(*aws, loop=None, return_exceptions=False):
    return asyncio.gather(aws, loop, return_exceptions)

и использовать ее как это:

async def main():
    try:
        await in_parallel(
            some_work("work1", 3),
            some_work("work2", 1),
            some_work("work3", 2),
            in_parallel(
                some_work("work4", 3),
                some_work("work5", 1, raise_exception=True),
                some_work("work6", 2)
            )
        )

    except RuntimeError as error:
        print(error)


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

И получил кучу ошибок:

D: /Archive/Projects/PycharmProjects/test/asyncio_gather.py: 34: RuntimeWarning: coroutine 'some_work никогда не ожидался in_parallel (RuntimeWarning: включить tracemallo c, чтобы получить трассировку выделения объекта (последний вызов был последним): файл "D: /Archive/Projects/PycharmProjects/test/asyncio_gather.py", строка 46, в asyncio .run (main ()) Файл "C: \ Program Files \ Python38 \ lib \ asyncio \ runners.py", строка 43, в прогоне возвращает l oop .run_until_complete (основной) файл "C: \ Программные файлы \ Python38 \ lib \ asyncio \ base_events.py ", строка 612, в run_until_complete, возвращают файл future.result ()" D: /Archive/Projects/PycharmProjects/test/asyncio_gather.py ", строка 34, в основной in_parallel ( Файл "D: /Archive/Projects/PycharmProjects/test/asyncio_gather.py", строка 14, in_parallel, возвращает asyncio.gather (aws, l oop, return_exceptions) Файл "C: \ Program Files \ Python38 \ lib \ asyncio \ tasks.py", строка 806, в сборке fut = sure_future (arg, loop = l oop) Файл "C: \ Program Files \ Python38 \ lib \ asyncio \ tasks.py", строка 673, в Аргументе Обеспечить_объявление TypeError ('Asyncio.Future, сопрограмма или ожидаемый объект) is 'TypeError: Asyncio.Future, требуется сопрограмма или ожидаемый sys: 1: RuntimeWarning: сопрограмма' some_work 'никогда не ожидалась

Может кто-нибудь объяснить, почему? Это просто обертка!

1 Ответ

1 голос
/ 04 апреля 2020

Кто-нибудь может объяснить, почему? Это просто оболочка!

Оболочка имеет правильную подпись, но она неправильно вызывает asyncio.gather:

def in_parallel(*aws, loop=None, return_exceptions=False):
    return asyncio.gather(aws, loop, return_exceptions)

asyncio.gather ожидает ожидаемых быть переданным как позиционные аргументы, как вы вызывали его в первой версии вашего кода. Когда вы вызываете его из оболочки, вы всегда передаете ему ровно три позиционных аргумента: aws (содержащий кортеж ожидаемых значений, переданных в in_parallel), loop (всегда None при вызове) и return_exceptions (логическое значение). Ни один из них не является реально ожидаемым, поэтому gather вызывает исключение, как только он пытается что-то сделать с полученными им "ожидаемыми", такими как преобразование их в фьючерсы.

Правильный способ вызова gather из in_parallel заключается в использовании оператора * для передачи каждого элемента aws в качестве отдельного позиционного аргумента и для передачи loop в качестве аргумента ключевого слова, как вы уже делаете с return_exceptions:

def in_parallel(*aws, loop=None, return_exceptions=False):
    return asyncio.gather(*aws, loop=loop, return_exceptions=return_exceptions)

С этой модификацией ваш код работает как положено. Наконец, обратите внимание, что явный аргумент loop является устаревшим , поэтому вы можете опустить его в своей оболочке.

...