Every asyncio program starts here: without an entry point and await, nothing runs.
A function defined with async def is a coroutine function. Calling it does not run its body; it returns a coroutine object, a paused computation waiting to be driven. Something has to run it, and that something is the event loop.
asyncio.run(main()) creates a fresh event loop, runs the coroutine main() to completion, closes the loop and returns the result. Call it once, at the top of your program; inside async code you never call it again.
Inside a coroutine, await expr runs another awaitable and gives you its result. While the awaited thing is waiting (a timer, a socket, a subprocess), the event loop is free to run other code. That is the whole trick: concurrency comes from coroutines voluntarily pausing at await points, all on one thread.
awaitonly works insideasync def(or the asyncio REPL,python -m asyncio).- Awaitables are coroutines, Tasks and Futures.
- Calling a coroutine function without awaiting it creates an object that never runs, and Python warns "coroutine ... was never awaited".
Async code is "colored": an async function can call sync functions freely, but a sync function cannot await. That is why asyncio programs have a single asyncio.run() at the edge and async all the way down from there.
Example
import asyncio
async def greet(name: str) -> str:
await asyncio.sleep(0.1) # pause; the loop could run other work
return f"hello, {name}"
async def main() -> None:
coro = greet("ada")
print(type(coro).__name__) # coroutine (nothing has run yet)
print(await coro) # hello, ada
result = asyncio.run(main())
print(result) # NoneRemember
- Calling an async def function only creates a coroutine object
- await runs an awaitable and yields control while it waits
- asyncio.run() is the single sync-to-async entry point
Watch out for
- Forgetting await: the coroutine never runs and you get a RuntimeWarning instead of a result.