Python async & await
In this page:
import asyncio
async def coroutine_name():
result = await other_coroutine()
return result
asyncio.run(coroutine_name())
What is Asynchronous Programming?
Asynchronous programming lets a single thread juggle many I/O-bound operations -- network calls, file reads, database queries -- without blocking on any one of them.
While one task waits on a slow response, Python's event loop switches to run other ready tasks, which is far cheaper than spinning up threads for the same workload.
Example: What is Asynchronous Programming?
import asyncio
async def main():
print("Doing work without blocking a thread")
asyncio.run(main()) # runs the coroutine on the event loop
Defining Coroutines with async
Prefixing a function definition with async turns it into a coroutine: calling it doesn't run the body immediately, it returns a coroutine object that must be awaited or scheduled to actually execute.
This distinction trips up beginners who expect async def functions to behave like regular ones.
Example: Defining Coroutines with async
async def greet():
return "hello"
coro = greet() # calling it doesn't run the body, just returns a coroutine object
print(type(coro))
coro.close() # closes without running the coroutine's body
Awaiting Coroutines with await
The await keyword pauses the current coroutine at that point and hands control back to the event loop until the awaited operation completes, then resumes exactly where it left off.
You can only use await inside another async def function -- using it at the top level of ordinary code is a syntax error.
Example: Awaiting Coroutines with await
import asyncio
async def get_value():
await asyncio.sleep(0) # pauses here and hands control back to the event loop
return 42
async def main():
value = await get_value() # waits for get_value to complete
print(value)
asyncio.run(main())
Running Coroutines
asyncio.run() is the standard entry point for a synchronous script: it creates a fresh event loop, runs the given coroutine to completion, and cleanly shuts the loop down afterward.
It should typically be called only once, at the very top level of your program.
Example: Running Coroutines
import asyncio
async def main():
print("Started with asyncio.run()")
asyncio.run(main()) # creates an event loop, runs main(), then shuts it down
Running Concurrent Tasks
asyncio.gather() takes multiple coroutines, schedules them to run concurrently on the event loop, and returns their results together once all of them finish.
This is how you get real speedup from async code -- awaiting coroutines one after another in sequence gives you no concurrency benefit at all.
Example: Running Concurrent Tasks
import asyncio
async def task(n):
await asyncio.sleep(0)
return n * 2
async def main():
results = await asyncio.gather(task(1), task(2), task(3)) # runs all three concurrently
print(results)
asyncio.run(main())
- Calling a coroutine without
await, as infetch(), which only creates a coroutine object and never runs it. - Using
time.sleep()insideasynccode, which blocks the whole event loop; useawait asyncio.sleep(). - Using
awaitin a normaldeffunction, which is aSyntaxError; the function must beasync def.
Chapter Quiz — Complete all 12 topics to unlock
0/12 topics done
Complete these topics first: