Edgar Navasardyan
Edgar Navasardyan

Reputation: 4501

Proper way to retrieve the result of tasks in asyncio

I am trying to examine the wait method of the asyncio module. I have this primitive test app to serve as a playground:

import asyncio

async def foo():
    return 1  # datetime.datetime.now()

async def entry_point():
    coros = [foo()]*3
    done, pending = await asyncio.wait(coros, return_when=asyncio.FIRST_COMPLETED)
    for obj in done:
        print(obj._result)  # works, but seem dirty

asyncio.run(entry_point())

Basically, my goal is to get the result of the first completed task. And here's I am a bit confused in terminology. The docs says that asyncio.wait

Returns two sets of Tasks/Futures: (done, pending).

How do I know whether the object is a task or a future? And the main question is, how do I extract the result of the successfully ended task ? One way is to access the protected attribute _result (as is shown in my code snippet). But I am feeling like there's a cleaner way to do that. What is the proper pattern to achieve that?

https://docs.python.org/3/library/asyncio-task.html#waiting-primitives

Upvotes: 0

Views: 1903

Answers (1)

HTF
HTF

Reputation: 7260

The doc for asyncio.wait has the following note:

Deprecated since version 3.8, will be removed in version 3.11: Passing coroutine objects to wait() directly is deprecated.

Therefore you should use asyncio.Task that also has the asyncio.Task.result method:

test.py:

import asyncio
import random


async def task(i):
    t = random.uniform(1, 5)
    print(f"START: {i} ({t:.3f}s)")
    await asyncio.sleep(t)
    print(f"END: {i}")

    return i


async def main():
    tasks = []

    for i in range(5):
        tasks.append(asyncio.create_task(task(i)))

    done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)

    for t in done:
        print(t.result())


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

Test:

$ python test.py
START: 0 (2.743s)
START: 1 (2.490s)
START: 2 (4.785s)
START: 3 (3.746s)
START: 4 (1.010s)
END: 4
4

If you want to retrieve all results and get the earliest next result first, use asyncio.as_completed instead:

...
for t in asyncio.as_completed(tasks):
    print(await t)
...

Test 2:

$ python test.py
START: 0 (2.155s)
START: 1 (1.309s)
START: 2 (3.380s)
START: 3 (3.451s)
START: 4 (1.587s)
END: 1
1
END: 4
4
END: 0
0
END: 2
2
END: 3
3

Upvotes: 1

Related Questions