Async Python Tricks
Starting an Async Program
In Python 3.7 and above, you can use asyncio.run
to easily start your async program:
async def async_main():
await asyncio.sleep(1)
print("Async")
def main():
asyncio.run(async_main(), debug=True)
if __name__ == "__main__":
main()
Alternatives
To run an async program in other ways, you need to use the loop directly.
loop = asyncio.get_running_loop()
loop.run_until_complete
if you have one function you need to runloop.run_forever
if you never want to close the async loop
Running blocking synchronous functions after Python 3.9
You should avoid calling blocking I/O functions inside async code.
Instead, try to run the code using asyncio.to_thread
.
await asyncio.to_thread(blocking_function)
This is available in Python 3.9 and above.
If your blocking functions share state or require some sort of synchronization, you will have to make sure that running them concurrently on multiple threads does not affect them.
Running all blocking code in a single thread
To avoid having to synchronize the threads started by asyncio.to_thread
,
you can use run_in_executor
with a thread pool
with a single thread (max_workers=1
) so your blocking tasks are all run in the same thread.
Instead of
await asyncio.to_thread(blocking_function)
use:
async_executor = ThreadPoolExecutor(
max_workers=1, thread_name_prefix="async_worker_thread"
)
await asyncio.get_running_loop().run_in_executor(
async_executor, blocking_function
)
For debugging purposes, give the executor a reasonable name related to
the blocking functions you’ll run on it.
Simply replace "async_worker_thread"
.
This should work in versions before Python 3.9.
Internally, asyncio.to_thread
uses loop.run_in_executor
in a very similar way.
The problem is the blocking functions must wait for other blocking functions to finish. You could use multiple worker pools to group similar blocking tasks, e.g. one worker pool for redis connections.
Running blocking synchronous functions before Python 3.9
If you don’t have asyncio.to_thread
or you don’t want to run tasks in a single thread,
you can use run_in_executor
with the default executor:
await loop.run_in_executor(executor=None, func=blocking_func)
Starting a coroutine from a thread
If you are running code in a thread but need to start an asynchronous function,
you can use asyncio.run_coroutine_threadsafe
to request that the async loop run the function.
You need access to the loop.
asyncio.run_coroutine_threadsafe(coro, loop)
This is available in Python 3.5.1 and above.
Wait for result
You can use the optional timeout
parameter to specify how long to wait.
asyncio.run_coroutine_threadsafe(coro, loop).result(timeout)
Optimization: Use a faster async loop
Use uvloop instead of the built-in loop for a potential performance increase.
Call uvloop.install() before calling asyncio.run() or manually creating an asyncio event loop.
What can run asynchronously?
Built-in
- TCP, Unix sockets with the builtin asyncio streams
- External processes, with
asyncio.create_subprocess_exec
andasyncio.create_subprocess_shell
- Queue putting and getting with an
asyncio.Queue
- Other async synchronization,
such as
asyncio.Lock
orasyncio.Event
. Compare to thread synchronization APIs, such asthreading.Lock
. - Callback-based code bridged with async code using Futures
- Signal handling with
loop.add_signal_handler
External libraries
There is support for many types of network protocols, and other I/O bound tasks. Many projects have official libraries that support asyncio.
- aiohttp is a popular client and server HTTP library.
- Many HTTP APIs, such as FastAPI support running async code in response to an HTTP request.
- NATS with the official async library. It had significant changes with the v2 release, including Python 3.10 support.
- Kafka with aiokafka.
- ZeroMQ with the official library or aiozmq, which must use a custom async loop.
- Redis with either aioredis or asyncio-redis, which is not actively maintained.
- Memcache, redis, or in-memory caching with aiocache.
- Databases with asyncpg or databases.
- DNS with aiodns.
- Serial device access with aioserial
- Logging asynchronously with aiologger. Make sure to use the right repository.
See awesome-asyncio.