AnyIO lets you write asynchronous code in Python that works with different async backends.
It gives you a single interface to work with asyncio, trio, and other async libraries. This means you can write your code once and run it anywhere.
This guide will show you how to use AnyIO effectively in your Python projects.
\Prerequisites
Ensure you have installed Python 3.7 or a newer version on your computer. You should already know basic Python and have some experience with asynchronous programming.
Step 1 — Getting started with AnyIO
Start by creating a new folder for your project and setting up a Python virtual environment:
Activate your virtual environment:
Install AnyIO:
Let's create a simple example. Make a file called main.py and add this code:
This example highlights the basic structure of AnyIO programs: define functions with async def, use await to pause operations, and start the program with anyio.run(). By default, AnyIO uses asyncio, but you can easily switch to trio without changing your code.
Run it with:
You'll see:
Now that you've seen how to set up a basic AnyIO program and understand its structure, let's move on to the next step.
Step 2 — Working with tasks and concurrency
Asynchronous programming allows you to run multiple tasks concurrently, improving performance by handling operations simultaneously, especially for I/O-bound tasks like file reading, network requests, or database queries.
In this step, you'll learn to use AnyIO’s task groups to run multiple tasks simultaneously. Task groups let you manage and execute tasks concurrently, ensuring all tasks finish before moving forward.
Let's see how to implement this using AnyIO:
The create_task_group() creates a group of tasks that run together. You add tasks to the group with start_soon(). When you exit the task group, it waits for all tasks to finish.
Run this script:
You'll see something like:
Notice how the tasks run at the same time, not one after another. If they ran one after another, it would take 6 seconds (2+1+3). But running them together takes only 3 seconds - just the time needed for the longest task.
This shows the power of async programming: you can do multiple things at once without blocking.
Step 3 — Error handling and cancellation
In real-world async programs, error handling and task cancellation are essential for maintaining stability and ensuring resources aren't wasted when something goes wrong. AnyIO provides built-in tools to handle errors and cancel tasks gracefully. In this step, you'll learn how to manage errors and cancellations in an async program using AnyIO.
We’ll walk through a new example where a task raises an error, and the other tasks in the group are cancelled as a result. This example demonstrates how AnyIO handles errors and ensures that tasks are properly cancelled when one fails.
Here’s the new example:
In this example, failing_task() raises an error after one second. When this happens, the entire task group gets cancelled, including long_task(). The get_cancelled_exc_class() function gives you the right cancellation exception class.
Run the script:
You'll see output like:
When failing_task() raises an error, long_task() gets cancelled, and the error goes up to the main function where it's caught. This shows AnyIO's structured approach: when one task fails, all related tasks get cancelled to prevent wasting resources.
Step 4 — Working with timeouts
In asynchronous applications, especially those involving network operations or external services, operations can sometimes hang indefinitely. Timeouts are essential to prevent your application from freezing when something goes wrong. AnyIO provides elegant solutions for implementing timeouts in your async code.
In this step, you'll learn how to use AnyIO's timeout mechanisms to make your applications more resilient. You'll create a reusable timeout decorator that can be applied to any async function.
This decorator uses AnyIO's move_on_after() context manager to set a timeout for any function it wraps. If the function doesn't complete within the specified timeout, the operation is cancelled and a TimeoutError is raised.
Now, let's clear the existing code in main.py and replace it with the following to test our timeout decorator:
In this example, we've created two functions with different execution times:
- fast_operation() completes within the 2-second timeout
- slow_operation() takes 3 seconds and exceeds the timeout
When we run this code, the timeout decorator will allow fast_operation() to complete normally, but will cancel slow_operation() after 2 seconds and raise a TimeoutError.
Run the script:
You'll see output like:
Notice that slow_operation() starts but never completes its execution. The timeout decorator cancels it after 2 seconds and raises a TimeoutError, which we catch in the main function.
Timeouts are particularly valuable in real-world applications where you interact with external services that might be slow or unresponsive. Implementing timeouts prevents your application from hanging indefinitely when something goes wrong, ensuring a better user experience and more efficient resource usage.
Final thoughts
In this guide, you've learned how to use AnyIO for Python async programming, covering key concepts like task groups, error handling, and timeouts.
The main advantage of AnyIO is its ability to abstract the differences between asyncio and trio, allowing you to focus on your application logic rather than the complexities of the async frameworks. To dive deeper into AnyIO, check out the official AnyIO documentation.
Happy coding!