Python Tutorial menu

Python async and await

async and await let one Python program wait for many slow things at the same time, instead of one after another.

Why Async?

Think of a tea stall. The worker puts water on to boil. A slow worker stands and watches the pot until it boils, and only then takes the next order. A smart worker takes more orders while the water heats.

Programs often wait too: for a web page to download, a database to answer, or a file to arrive. Asynchronous (async) code lets Python start other work during those waits.

WordMeaning
async defDefines a coroutine: a function that can pause
await"Pause here until this finishes, and let other work run meanwhile"
asyncioThe built-in module that runs coroutines
Event loopThe manager that switches between waiting coroutines
TaskA coroutine that has been scheduled to run

Your First Coroutine

Example

python
import asyncio

async def greet(name):
    print(f"Hello {name}...")
    await asyncio.sleep(1)
    print(f"...goodbye {name}")

asyncio.run(greet("Ayesha"))
Output
Hello Ayesha...
...goodbye Ayesha
  • async def makes greet a coroutine.
  • await asyncio.sleep(1) pauses for one second without blocking the program.
  • asyncio.run() starts the event loop and runs the coroutine. You call it once, at the top of your program.

Calling greet("Ayesha") on its own does not run the code. It only creates a coroutine object. You must await it or pass it to asyncio.run().

One After Another

First, the slow way. Each order waits for the one before it.

Example

python
import asyncio
import time

async def make(item, seconds):
    await asyncio.sleep(seconds)
    return f"{item} ready"

async def main():
    start = time.perf_counter()
    a = await make("Tea", 2)
    b = await make("Paratha", 3)
    c = await make("Omelette", 1)
    print(a, b, c, sep=" | ")
    print(f"Took {time.perf_counter() - start:.0f} seconds")

asyncio.run(main())
Output
Tea ready | Paratha ready | Omelette ready
Took 6 seconds

2 + 3 + 1 = 6 seconds. We used async, but we still waited for each item in turn.

At the Same Time With gather()

asyncio.gather() runs several coroutines together and returns all their results, in the same order you passed them.

Example

python
import asyncio
import time

async def make(item, seconds):
    await asyncio.sleep(seconds)
    print(f"  {item} done after {seconds}s")
    return f"{item} ready"

async def main():
    start = time.perf_counter()
    results = await asyncio.gather(
        make("Tea", 2),
        make("Paratha", 3),
        make("Omelette", 1),
    )
    print(results)
    print(f"Took {time.perf_counter() - start:.0f} seconds")

asyncio.run(main())
Output
  Omelette done after 1s
  Tea done after 2s
  Paratha done after 3s
['Tea ready', 'Paratha ready', 'Omelette ready']
Took 3 seconds

Now the whole order takes only as long as the slowest item: 3 seconds instead of 6. The omelette finished first, but results still keeps the original order.

Tasks With TaskGroup

From Python 3.11, asyncio.TaskGroup is the modern, safer way to start many tasks. If one task fails, the others are cancelled cleanly.

Example

python
import asyncio

async def download(page, seconds):
    await asyncio.sleep(seconds)
    return f"{page} ({seconds}s)"

async def main():
    async with asyncio.TaskGroup() as tg:
        t1 = tg.create_task(download("home", 0.3))
        t2 = tg.create_task(download("courses", 0.1))
        t3 = tg.create_task(download("contact", 0.2))
    print(t1.result(), t2.result(), t3.result(), sep=", ")

asyncio.run(main())
Output
home (0.3s), courses (0.1s), contact (0.2s)

When the async with block ends, every task in the group has finished.

Timeouts

Do not wait forever for something slow. asyncio.timeout() (Python 3.11+) stops the work after a limit.

Example

python
import asyncio

async def slow_server():
    await asyncio.sleep(5)
    return "data"

async def main():
    try:
        async with asyncio.timeout(1):
            print(await slow_server())
    except TimeoutError:
        print("Server too slow, showing saved data instead")

asyncio.run(main())
Output
Server too slow, showing saved data instead

A Common Mistake: Blocking Code

Inside a coroutine, never use a normal blocking call like time.sleep() or requests.get(). It freezes the whole event loop, so nothing else can run.

Example

python
import asyncio
import time

async def bad(n):
    time.sleep(1)
    return n

async def main():
    start = time.perf_counter()
    await asyncio.gather(bad(1), bad(2), bad(3))
    print(f"Took {time.perf_counter() - start:.0f} seconds")

asyncio.run(main())
Output
Took 3 seconds

Three tasks ran one by one because time.sleep() does not let go. With await asyncio.sleep(1) it would take 1 second. For web requests, use an async library such as httpx or aiohttp instead of requests.

When Should You Use Async?

Good fitNot a good fit
Calling many APIs or web pagesHeavy maths or image processing
Chat servers and botsSimple scripts that do one thing
Web frameworks like FastAPICode that only reads a few local files

Async speeds up waiting, not thinking. If your program is slow because the CPU is busy calculating, async will not help. Look at the multiprocessing module for that.

Exercise

Exercise

Three coroutines sleep for 1, 2 and 4 seconds. About how long does asyncio.gather() take to run all three?

Learn Python with a teacher

Join the Python Programming course at Workownix, online or in Orangi Town, Karachi, and get a certificate.

See the course Ask on WhatsApp