7 Python Async Generator Things I Wish I Knew Earlier
1) Definition: Regular VS Async Generators
Regular Generator
A regular generator is a function that uses the yield keyword. It produces values lazily — only when invoked rather than all at once.
Async Generator
An async generator is a coroutine function that uses yield. In other words, it must use async def and yield.
Similar to a regular generator, it produces values lazily — only when invoked rather than all at once.
2) Using Regular VS Async Generators
Regular generators
To use regular generators, we can either iterate through it or use next()
Async generators
When dealing with async generators, we need to use:
async forinstead offoranext()instead ofnext()(plus we need toawaitthis)
3) Benefits of Regular VS Async Generators
You might be wondering — isn’t async generators like regular generators with extra steps? A valid concern, but there are some key benefits async generators have over regular generators.
Regular generators are blocking ; Async generators are not
Regular generators cannot use await, and block the event loop. While waiting for a regular generator, other tasks are blocked.
Async generators can use await, and does not block the event loop. While waiting for an async generator, other tasks are not blocked.
In other words, when async generators are waiting, other coroutines are able to run. When regular generators are waiting, nothing else can run.
Note — this applies largely to I/O-bound tasks
Quick Pause
Consider buying my ebook — 256 Python Things I Wish I Knew Earlier — a collection of hard-earned insights from writing 500,000+ lines of code over 10 years. Save yourself years of trial and error.
Link: https://payhip.com/b/xpRco
4) Consumer vs Producer
Producer
The one that produces items. Usually the generator itself.
Consumer
The one that asks for items from the producer. Usually the code iterating over the generator.
How control flows
consumer requests next item from producer
consumer gives control to producer
producer does its stuff, and yields a value
producer pauses until the next request
producer gives control to consumer
5) When to NOT use async generators
I/O-bound tasks VS CPU-bound tasks
A task is I/O-bound if it is slow because of I/O (input/output). Common I/O-bound tasks include reading/writing files, HTTP requests, database queries etc.
A task is CPU-bound if it is slow because of CPU processing power. Common CPU-bound tasks include mathematical computations, image processing, machine learning etc.
The Global Interpreter Lock (GIL)
In Python, the Global Interpreter Lock (GIL) ensure that only 1 thread has control over the Python interpreter at any one point in time. In other words, there can exist multiple threads, but at any single point in time, only 1 thread is actually running.
Async only helps with I/O-bound tasks
When multiple I/O-bound tasks are being handled by asyncio, the Python interpreter context-switches quickly between them while waiting for I/O. A new task no longer needs to wait for an older task to finish before starting.
This is why asyncio works when dealing with I/O-bound tasks
Async doesn’t help with CPU-bound tasks
When multiple CPU-bound tasks are being handled by asyncio, context-switching doesn’t help as only 1 thread takes control of the Python interpreter at any time. Which means that context-switching is as good as running the CPU-bound tasks sequentially.
This extends to async generators as well — using async generators in place of regular generators won’t help if tasks are mostly CPU-bound.
6) Built-in Backpressure Support
Backpressure is a mechanism that prevents a fast producer from overwhelming a slow consumer. In other words, if a consumer is slow, the producer waits until the consumer is ready for the next item.
Async generators in Python are pull-based (instead of push-based) — producers only produce when the consumer is ready to accept the next yield item.
As such, the producer is unable to overwhelm the consumer as the pace of consumption is set by the consumer.
7) .aclose() and .athrow()
.aclose() allows us to cleanly close our async generator.
After closing our async generator, we are no longer able to iterate through it.
.athrow() allows us to intentionally inject an exception into our async generator
Conclusion
Hopefully this was clear and easy to understand
If You Wish To Support Me As A Writer
Buy my Ebook — 256 Python Things I Wish I Knew Earlier at https://payhip.com/b/xpRco — a collection of hard-earned insights from writing 500,000+ lines of code over 10+ years. Save yourself from years of trial and error
Thank you — these actions go a long way, and I really appreciate it.








