asyncio

Use the async/await syntax to declare coroutines, then use asyncio.run() to schedule it.

import asyncio

async def main():
    await asyncio.sleep(1)
    print('done')

asyncio.run(main())

Wait for multiple tasks to complete

Use asyncio.gather

import asyncio

async def task(id):
    await asyncio.sleep(1)
    print(id)
    return id

async def main():
    tasks = [task(i) for i in range(5)]

    results = await asyncio.gather(*tasks)
    print(results)

asyncio.run(main())

Output:

0
1
2
3
4
[0, 1, 2, 3, 4]

TaskGroup

TaskGroup works like WaitGroup or Barrier in other languages, but you don't have to explicitly wait for it.

import asyncio
import random

async def task(id):
    await asyncio.sleep(random.random())
    print(id)
    return id

async def main():

    async with asyncio.TaskGroup() as g:
        task1 = g.create_task(task(1))
        task2 = g.create_task(task(2))
    print(f"done: {task1.result()}, {task2.result()}")

asyncio.run(main())

Handling exceptions

asyncio.gather works much like Promise.all, if an exception is raised, it immediately propagated to the task that awaits on gather(). And other tasks will continue to run.

asyncio.gather also accept keyword argument return_exceptions, when set to True, it works more like Promise.allSettled.

import asyncio

async def task(id):
    await asyncio.sleep(1)
    print(id)
    return id

async def main():
    tasks = [task(i) for i in range(5)]

    results = await asyncio.gather(*tasks, return_exceptions=True)
    print(results)

asyncio.run(main())

Output:

[ZeroDivisionError('division by zero'), 1.0, 0.5, 0.3333333333333333, 0.25]

Wait for the first task

asyncio.wait gives you more control than asyncio.gather:

import asyncio
import random

async def task(id):
    await asyncio.sleep(random.random())
    print(id)
    return id

async def main():
    # must be a list of Tasks, not coroutines
    tasks = [asyncio.create_task(task(i)) for i in range(5)]

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

    print("done", [x.result() for x in done])
    print("pending tasks:", len(pending))
    for x in pending:
        x.cancel()

asyncio.run(main())

Output:

2
done [2]
pending tasks: 4

Timeout

When timed out, the task is also canceled.

import asyncio
import random

async def task():
    await asyncio.sleep(random.random())
    print("done")

async def main():
    try:
        async with asyncio.timeout(0.5):
            await task()
    except TimeoutError:
        print("Timed out")

asyncio.run(main())