AI Hints
Home›Python Tutorial›Concurrency: Threads, Processes and Asyncio
Python · Lesson 29 of 49

Asyncio

Thousands of waits, one thread.

⏱ 2 min read▶ Video at 1:29:55🎓 Free
Watch this lesson in the Python courseWatch on YouTube ↗Full Python course

Ava's website now answers thousands of customers at once. Giving every customer a thread would waste memory. A waiter in a restaurant does not stand next to one table until the guests finish eating. They take an order, move to another table, and come back when the food is ready. Asyncio works like that waiter.

Asyncio
From the video: Asyncio
  • Thousands of customers at once
  • One worker, many waiting tasks
  • Switch tasks while waiting

async and await

An async def function is a coroutine. Calling it does not run it. You await it, which means, pause here, and let others work until this finishes. The asyncio dot run function starts the event loop, that coordinates everything. Here, one coroutine waits a little, and returns a message.

async1.pypython
import asyncio

async def fetch(name):
    await asyncio.sleep(0.2)
    return f"{name} ready"

async def main():
    print(await fetch("fern"))

asyncio.run(main())
Output
fern ready

Running many at once with gather

The real power is gather. It starts many coroutines at once, and waits for them all. Each one sleeps, but they overlap, so five waits take about the time of one. Compare the total time to the sequential version, and see the difference. One thread, and no locks.

async2.pypython
import asyncio, time

async def check(name):
    await asyncio.sleep(0.2)
    return f"{name} ok"

async def main():
    start = time.perf_counter()
    results = await asyncio.gather(*(check(n) for n in "abcde"))
    print(results)
    print("seconds:", round(time.perf_counter() - start, 1))

asyncio.run(main())
Output
['a ok', 'b ok', 'c ok', 'd ok', 'e ok']
seconds: 0.2

Tasks, timeouts and errors

You can create tasks that run in the background, and set a timeout, so that a slow service cannot freeze everything. Wait for with a timeout raises an error, if the work takes too long, and you catch it. Real programs use these tools to stay responsive, even when other systems are slow.

async3.pypython
import asyncio

async def slow():
    await asyncio.sleep(1)
    return "done"

async def main():
    try:
        r = await asyncio.wait_for(slow(), timeout=0.1)
    except asyncio.TimeoutError:
        print("too slow, giving up")
    task = asyncio.create_task(asyncio.sleep(0.05, "bg"))
    print(await task)

asyncio.run(main())
Output
too slow, giving up
bg

Which tool when?

So how do you choose? For many waiting jobs, with modern libraries that support it, asyncio scales best. For waiting jobs with ordinary libraries, use threads. For heavy calculations, use processes. And for simple scripts, sequential code is perfectly fine. Measure before you optimise, because simple code is easier to maintain.

SituationBest tool
Thousands of network waitsasyncio
A few waits, ordinary librariesthreads
Heavy number crunchingprocesses
Simple scriptplain code

Concurrent fetches

Your turn. Write an async function that sleeps briefly, and returns a name. Use gather, to run three of them at once, and print the list of results. Ready? Here is one solution. A coroutine, a gather with three calls, and asyncio run.

✍️ Exercise: Concurrent fetches

Write an async fetch(name) that sleeps 0.1s and returns the name; gather three and print the list.

Show hint

await asyncio.gather(...)

Show solution
gather.pypython
import asyncio

async def fetch(name):
    await asyncio.sleep(0.1)
    return name

async def main():
    print(await asyncio.gather(fetch("a"), fetch("b"), fetch("c")))

asyncio.run(main())
Output
['a', 'b', 'c']

Summary

Excellent. You understand async and await, you can run many coroutines with gather, and you can add timeouts and background tasks. With threads, processes and asyncio, you can pick the right tool for any waiting or heavy job. Next, we store data properly, in a real database.

Key takeaways
  • async def + await
  • asyncio.gather runs coroutines together
  • Choose: asyncio, threads, processes
Next up: Databases with SQLite — store and query data