2 minutes
Asyncio and async/await: Asynchronous Programming Made Pythonic
Welcome to part three of our Python 3 features series! Today, we’re digging into asyncio
and the async
/await
syntax that make asynchronous code feel right at home in Python.
When you print()
in Python, it waits until it’s done before moving on. But what if you want to do multiple things at once, like fetch data from the web, read files, and process user input, all without blocking? Enter asyncio
.
1. The Event Loop Basics
asyncio
(added in Python 3.4 via PEP 3156) revolves around an event loop, a little manager that runs your tasks and switches between them when they’re waiting.
import asyncio
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
# Old-school way on 3.4:
loop = asyncio.get_event_loop()
loop.run_until_complete(say_after(1, 'hello'))
loop.close()
Here, asyncio.sleep()
is non-blocking, you give control back to the loop, which can run other tasks in the meantime.
2. Running Multiple Tasks
Let’s run two coroutines side by side:
import asyncio
async def main():
task1 = asyncio.create_task(say_after(1, 'hello'))
task2 = asyncio.create_task(say_after(2, 'world'))
print('started at', asyncio.get_event_loop().time())
await task1
await task2
print('finished at', asyncio.get_event_loop().time())
asyncio.run(main()) # New in 3.7: handles loop setup/teardown
You’ll see “hello” after 1 second, “world” after 2 seconds, and timestamps showing overlap.
3. asyncio.run()
and Cleanup
Python 3.7 introduced asyncio.run()
, so you don’t have to manage the loop yourself. It sets up, runs your main coroutine, and tears down the loop neatly.
asyncio.run(main()) # instead of manually creating and closing the loop
4. await
Is Your New Best Friend
In Python 3.5 (PEP 492), the async
/await
keywords made coroutine code look like regular functions:
async def fetch_data(x):
print(f'Fetching {x}...')
await asyncio.sleep(1)
return f'Data {x}'
async def main():
results = await asyncio.gather(
fetch_data('A'),
fetch_data('B'),
fetch_data('C'),
)
print(results)
asyncio.run(main())
asyncio.gather()
runs coroutines in parallel and collects their results.
5. When to Use Asyncio
- IO-bound tasks: network requests, file reads/writes, database calls
- High concurrency: thousands of small tasks, websockets, chat servers
Don’t use it for CPU-heavy work, that still needs threads or processes.
That’s a quick tour of asyncio
and the modern async
/await
syntax. Next up, we’ll talk about type hints and variable annotations to make your code easier to understand and maintain. Happy coding!