Learn Python with Talk Python's 270 hours of courses

Higher level Python asyncio with AnyIO

Episode #385, published Sat, Oct 15, 2022, recorded Thu, Sep 29, 2022

Do you love Python's async and await but feel that you could use more flexibility and higher-order constructs like running a group of tasks and child tasks as a single operation, or streaming data between tasks, combining async tasks with multiprocessing or threads, or even async file support? You should check out AnyIO. On this episode we have Alex Grönholm the creator of AnyIO here to give us the whole story.

Watch this episode on YouTube
Play on YouTube
Watch the live stream version

Episode Deep Dive

Guests Introduction and Background

Alex Grönholm is a seasoned Python developer with a deep background in open-source projects, including AnyIO, Typeguard, SQLA Codegen, and more. He has spent years balancing a full-time job with maintaining multiple Python libraries, many of which focus on async programming, task scheduling, and type checking. Alex’s interests range from building foundational tools (like the Asphalt framework) to exploring advanced concurrency patterns in Python. His experiences juggling these projects and staying active in the Python community give him a broad perspective on where Python excels and how to make it even better.

What to Know If You're New to Python

If you’re new to Python and want to follow along with the discussion on asynchronous programming, concurrency, and libraries like AnyIO, here are a few pointers:

  • Understand the basics of functions and async/await keywords (introduced in Python 3.5).
  • Familiarize yourself with the idea of concurrent tasks versus sequential code.
  • Know that “async” code in Python generally runs on a single thread but can use multiple threads or subprocesses for certain tasks.
  • Have a basic grasp of reading and writing files in Python and how standard libraries like asyncio fit into the language.

Key Points and Takeaways

  1. AnyIO’s Purpose and Value AnyIO builds upon Python's built-in asyncio and blends ideas from the Trio library to provide higher-level abstractions. It enables more advanced concepts such as grouped tasks, powerful cancellation scopes, async file I/O (via threads), and more — without forcing you to switch your entire codebase to a brand-new async framework.
  2. Task Groups (Nurseries) and Cancellations A standout feature from Trio is the concept of “nurseries” or grouped tasks, which AnyIO brings into asyncio. Instead of managing each task separately, a task group can launch multiple async tasks and handle them as a unit, including canceling them all with a single command if needed. This simplifies error handling and cleanup.
  3. Cancel Scopes and Timeouts In AnyIO, you can apply cancel scopes to entire blocks of code, ensuring partial or nested tasks get canceled in a controlled way. Combined with timeouts (via move_on_after or fail_after), you can gracefully handle situations where a resource is unresponsive or slow. This approach differs from the built-in single-shot cancellation and brings more flexible, structured concurrency.
  4. Thread Integration: to_thread and from_thread AnyIO’s to_thread and from_thread functions provide clean ways to run synchronous code in a thread pool or bring code back from threads into the async event loop. This is crucial for accessing libraries or code that aren’t inherently async. It also respects Python’s context variables for advanced usage.
  5. Subprocess Handling Beyond threads, AnyIO also has a subprocess API (to_process) that can offload CPU-heavy or GIL-bound tasks to a separate process. It pickles arguments and return values, so the function you call in the subprocess needs to handle picklable data structures. This approach is often used for heavier computations or work that truly needs parallelism.
  6. Async File I/O Although true OS-level async file I/O remains tricky across platforms, AnyIO simulates it by using thread pools under the hood. You can open files with anyio.open_file (an async context manager), read and write asynchronously, or iterate through lines with an async for loop. This pattern avoids blocking the main event loop while dealing with file operations.
  7. Streaming Abstractions (Object and Byte Streams) AnyIO implements a powerful streaming abstraction that goes beyond just reading bytes. It allows for object streaming and layered streams (e.g., adding TLS on top of existing TCP). This is reminiscent of the “pluggable transports” idea, meaning you can have flexible data transformations or different protocols seamlessly stacked.
  8. Typed Attributes for Streams and Other Abstractions Typed attributes allow you to attach extra metadata or features to a stream. For example, you might fetch a remote IP address for logging or request a TLS certificate without having to rewrite your entire code to pass these objects around. It’s a consistent and composable way to query deeper context from layered streams.
  9. Other Notable Projects by Alex Alex developed SQLA Codegen to reflect an existing database schema and generate SQLAlchemy model code. He also authored Typeguard, a runtime type checker that can be integrated with pytest for ensuring your function arguments and return values align with type hints. Both are widely used for reducing boilerplate and catching issues early.
  10. Using Trio Documentation and Other Inspiration While AnyIO has its own documentation, Alex points out that much of the conceptual foundation is borrowed from Trio’s approach to structured concurrency. Therefore, reading the Trio docs is often a good way to understand the “why” and “how” behind AnyIO’s key concepts.

Interesting Quotes and Stories

  • On Balancing Multiple Open-Source Projects: “I manage so many projects that sometimes I get a kind of writer’s block. When that happens, I just step away or switch to another project until I’m unstuck.”
  • Regarding SQLA Codegen: “If you have a really large database, this will save literal hours of time.”

Key Definitions and Terms

  • Cancel Scope: A mechanism that allows you to cancel multiple tasks together. Once canceled, any code that awaits within that scope also sees cancellation until it exits the scope.
  • Nursery / Task Group: A structured concurrency concept that groups tasks into a single context, ensuring they complete or fail together.
  • Thread Pool: A collection of worker threads used to run blocking or CPU-bound tasks in parallel with async code.
  • Picklable: An object that can be serialized (pickled) by Python’s pickle module to be sent between processes.

Learning Resources

Below are a few resources to strengthen your Python foundations and explore async programming in more depth.

Overall Takeaway

AnyIO brings a powerful, Trio-inspired approach to async in Python without forcing you to abandon asyncio or rewrite existing code. It addresses real pain points in concurrency, such as structured cancellation, flexible timeouts, and bridging sync components with threads and subprocesses. Whether you’re building small services or architecting complex systems, AnyIO provides a cleaner, more consistent way to write async Python that integrates seamlessly with popular frameworks and libraries.

Links from the show

Alex: github.com/agronholm
AnyIO: anyio.readthedocs.io
sqlacodegen: github.com
apscheduler: github.com
typeguard: github.com
timescale: timescale.com
asphalt framework: github.com
Talk Python Trio episode: talkpython.fm/167
Trio: github.com
Poetry Package manager: python-poetry.org
Watch this episode on YouTube: youtube.com
Episode transcripts: talkpython.fm

--- Stay in touch with us ---
Subscribe to Talk Python on YouTube: youtube.com
Talk Python on Bluesky: @talkpython.fm at bsky.app
Talk Python on Mastodon: talkpython
Michael on Bluesky: @mkennedy.codes at bsky.app
Michael on Mastodon: mkennedy

Talk Python's Mastodon Michael Kennedy's Mastodon