Serdar Yegulalp
Senior Writer

How to use asyncio in Python

feature
Feb 19, 20206 mins
Development Libraries and FrameworksPythonSoftware Development

Take advantage of the high-level async functions in Pythonโ€™s asyncio library to write more efficient Python applications.

real time os nautilus clock against the clock future by raspirator getty
Credit: raspirator / Getty Images

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 asyncio work 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.

Serdar Yegulalp

Serdar Yegulalp is a senior writer at InfoWorld. A veteran technology journalist, Serdar has been writing about computers, operating systems, databases, programming, and other information technology topics for 30 years. Before joining InfoWorld in 2013, Serdar wrote for Windows Magazine, InformationWeek, Byte, and a slew of other publications. At InfoWorld, Serdar has covered software development, devops, containerization, machine learning, and artificial intelligence, winning several B2B journalism awards including a 2024 Neal Award and a 2025 Azbee Award for best instructional content and best how-to article, respectively. He currently focuses on software development tools and technologies and major programming languages including Python, Rust, Go, Zig, and Wasm. Tune into his weekly Dev with Serdar videos for programming tips and techniques and close looks at programming libraries and tools.

More from this author