Take advantage of the high-level async functions in Pythonโs asyncio library to write more efficient Python applications.
Pythonโs asynchronous programming functionality, or async for short, allows you to write programs that get more work done by not waiting for independent tasks to finish. The asyncio library included with Python gives you the tools to use async for processing disk or network I/O without making everything else wait.
asyncioย provides two kinds of APIs for dealing with asynchronous operations:ย high-levelย andย low-level. The high-level APIs are the most generally useful, and theyโre applicable to the widest variety of applications. The low-level APIs are powerful, but also complex, and used less frequently.
Weโll concentrate on the high-level APIs in this article. In the sections below, weโll walk through the most commonly used high-level APIs inย asyncio, and show how they can be used for common operations involving asynchronous tasks.ย
If youโre completely new to async in Python, or you could use a refresher on how it works, read my introduction to Python async before diving in here.
Run coroutines and tasks in Python
Naturally, the most common use for asyncio is to run the asynchronous parts of your Python script. This means learning to work with coroutines and tasks.ย
Pythonโs async components, including coroutines and tasks, can only be used with other async components, and not with conventional synchronous Python, so you needย asyncioย to bridge the gap. To do this, you use theย asyncio.runย function:
import asyncio
async def main():
print ("Waiting 5 seconds. ")
for _ in range(5):
await asyncio.sleep(1)
print (".")
print ("Finished waiting.")
asyncio.run(main())
This runsย main(), along with any coroutinesย main()ย fires off, and waits for a result to return.
As a general rule, a Python program should have only oneย .run()ย statement, just as a Python program should have only oneย main()ย function. Async, if used carelessly, can make the control flow of a program hard to read. Having a single entry point to a programโs async code keeps things from getting hairy.
Async functions can also be scheduled asย tasks, or objects that wrap coroutines and help run them.
async def my_task():
do_something()
task = asyncio.create_task(my_task())
my_task()ย is then run in the event loop, with its results stored inย task.
If you have only one task you want to get results from, you can useย asyncio.wait_for(task)ย to wait for the task to finish, then useย task.result()ย to retrieve its result. But if youโve scheduled a number of tasks to execute and you want to wait forย allย of them to finish, useย asyncio.wait([task1, task2])ย to gather the results. (Note that you can set a timeout for the operations if you donโt want them to run past a certain length of time.)
Manage an async event loop in Python
Another common use forย asyncioย is to manage the asyncย event loop. The event loop is an object that runs async functions and callbacks; itโs created automatically when you useย asyncio.run(). You generally want to use only one async event loop per program, again to keep things manageable.
If youโre writing more advanced software, such as a server, youโll need lower-level access to the event loop. To that end, you can โlift the hoodโ andย work directly with the event loopโs internals. But for simple jobs you wonโt need to.
Read and write data with streams in Python
The best scenarios for async are long-running network operations, where the application may block waiting for some other resource to return a result. To that end,ย asyncioย offersย streams, which are high-level mechanisms for performing network I/O. This includes acting as a server for network requests.
asyncioย uses two classes,ย StreamReaderย andย StreamWriter, to read and write from the network at a high level. If you want to read from the network, you would useย asyncio.open_connection()ย to open the connection. That function returns a tuple ofย StreamReaderย andย StreamWriterย objects, and you would useย .read()ย andย .write()ย methods on each to communicate.
To receive connections from remote hosts, useย asyncio.start_server(). The asyncio.start_server() function takes as an argument a callback function,ย client_connected_cb, which is called whenever it receives a request. That callback function takes instances ofย StreamReaderย and StreamWriterย as arguments, so you can handle the read/write logic for the server. (See hereย for an example of a simple HTTP server that uses theย asyncio-drivenย aiohttpย library.)
Synchronize tasks in Python
Asynchronous tasks tend to run in isolation, but sometimes you will want them to communicate with each other.ย asyncioย provides queues and several other mechanisms for synchronizing between tasks:
- Queues:ย
asyncioย queuesย allow asynchronous functions to line up Python objects to be consumed by other async functions โ for instance, to distribute workloads between different kinds of functions based on their behaviors. - Synchronization primitives:ย Locks, events, conditions, and semaphoresย in
asynciowork like their conventional Python counterparts.ย
One thing to keep in mind about all of these methods is that theyโreย notย thread-safe. This isnโt an issue for async tasks running in the same event loop. But if youโre trying to share information with tasks in a different event loop, OS thread, or process, youโll need to use theย threadingย moduleย and its objects to do that.
Further, if you want toย launchย coroutines across thread boundaries, use theย asyncio.run_coroutine_threadsafe()ย function, and pass the event loop to use with it as a parameter.
Pause a coroutine in Python
Another common use ofย asyncio, and an under-discussed one, is waiting for some arbitrary length of time inside a coroutine. You canโt useย time.sleep()ย for this, or youโll block the entire program. Instead, useย asyncio.sleep(), which allows other coroutines to continue running.
Use lower-level async in Python
Finally, if you think that the app youโre building may require asyncioโs lower-level components, take a look around before you start coding: Thereโs a good chance someone has already built an async-powered Python library that does what you need.
For instance, if you need async DNS querying, check theย aiodnsย library, and for async SSH sessions, thereโsย asyncSSH. Search PyPI by the keyword โasyncโ (plus other task-related keywords), or check the hand-curatedย Awesome Asyncioย list for ideas.


