Asyncio.gather vs asyncio.wait

前端 未结 5 1989
小鲜肉
小鲜肉 2020-12-04 05:11

asyncio.gather and asyncio.wait seem to have similar uses: I have a bunch of async things that I want to execute/wait for (not necessarily waiting for one to finish before t

5条回答
  •  独厮守ぢ
    2020-12-04 05:34

    A very important distinction, which is easy to miss, is the default bheavior of these two functions, when it comes to exceptions.


    I'll use this example to simulate a coroutine that will raise exceptions, sometimes -

    import asyncio
    import random
    
    
    async def a_flaky_tsk(i):
        await asyncio.sleep(i)  # bit of fuzz to simulate a real-world example
    
        if i % 2 == 0:
            print(i, "ok")
        else:
            print(i, "crashed!")
            raise ValueError
    
    coros = [a_flaky_tsk(i) for i in range(10)]
    

    await asyncio.gather(*coros) outputs -

    0 ok
    1 crashed!
    Traceback (most recent call last):
      File "/Users/dev/PycharmProjects/trading/xxx.py", line 20, in 
        asyncio.run(main())
      File "/Users/dev/.pyenv/versions/3.8.2/lib/python3.8/asyncio/runners.py", line 43, in run
        return loop.run_until_complete(main)
      File "/Users/dev/.pyenv/versions/3.8.2/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete
        return future.result()
      File "/Users/dev/PycharmProjects/trading/xxx.py", line 17, in main
        await asyncio.gather(*coros)
      File "/Users/dev/PycharmProjects/trading/xxx.py", line 12, in a_flaky_tsk
        raise ValueError
    ValueError
    

    As you can see, the coros after index 1 never got to execute.


    But await asyncio.wait(coros) continues to execute tasks, even if some of them fail -

    0 ok
    1 crashed!
    2 ok
    3 crashed!
    4 ok
    5 crashed!
    6 ok
    7 crashed!
    8 ok
    9 crashed!
    Task exception was never retrieved
    future:  exception=ValueError()>
    Traceback (most recent call last):
      File "/Users/dev/PycharmProjects/trading/xxx.py", line 12, in a_flaky_tsk
        raise ValueError
    ValueError
    Task exception was never retrieved
    future:  exception=ValueError()>
    Traceback (most recent call last):
      File "/Users/dev/PycharmProjects/trading/xxx.py", line 12, in a_flaky_tsk
        raise ValueError
    ValueError
    Task exception was never retrieved
    future:  exception=ValueError()>
    Traceback (most recent call last):
      File "/Users/dev/PycharmProjects/trading/xxx.py", line 12, in a_flaky_tsk
        raise ValueError
    ValueError
    Task exception was never retrieved
    future:  exception=ValueError()>
    Traceback (most recent call last):
      File "/Users/dev/PycharmProjects/trading/xxx.py", line 12, in a_flaky_tsk
        raise ValueError
    ValueError
    Task exception was never retrieved
    future:  exception=ValueError()>
    Traceback (most recent call last):
      File "/Users/dev/PycharmProjects/trading/xxx.py", line 12, in a_flaky_tsk
        raise ValueError
    ValueError
    

    Ofcourse, this behavior can be changed for both by using -

    asyncio.gather(..., return_exceptions=True)

    or,

    asyncio.wait([...], return_when=asyncio.FIRST_EXCEPTION)


    But it doesn't end here!

    Notice: Task exception was never retrieved in the logs above.

    asyncio.wait() won't re-raise exceptions from the child tasks until you await them individually. (The stacktrace in the logs are just messages, they cannot be caught!)

    done, pending = await asyncio.wait(coros)
    for tsk in done:
        try:
            await tsk
        except Exception as e:
            print("I caught:", repr(e))
    

    Output -

    0 ok
    1 crashed!
    2 ok
    3 crashed!
    4 ok
    5 crashed!
    6 ok
    7 crashed!
    8 ok
    9 crashed!
    I caught: ValueError()
    I caught: ValueError()
    I caught: ValueError()
    I caught: ValueError()
    I caught: ValueError()
    

    On the other hand, to catch exceptions with asyncio.gather(), you must -

    results = await asyncio.gather(*coros, return_exceptions=True)
    for result_or_exc in results:
        if isinstance(result_or_exc, Exception):
            print("I caught:", repr(result_or_exc))
    

    (Same output as before)

提交回复
热议问题