async with: a class and a generator
Watch the order of open, work and close. The third block shows that close still runs when the body fails.
async for loops over values that arrive over time, an async generator (async def with yield) produces them, async with opens and closes resources that need to await, and asyncio.Queue passes work between tasks. Streaming model tokens is the classic use.
Change the code and press Run (Ctrl+Enter). Predict what will happen first, then break it on purpose and read what changes. Your edits are saved and match the lesson page.
Ready for more? This phase has 6 hands-on exercises with automatic checks.
Practice questions
Write the code in the editor, run it, then open the model answer to compare.
Write an async generator countdown(n) that yields n, n-1, ... 1 with await asyncio.sleep(0.01) before each, and print the values joined by spaces.
Write an async generator take(stream, k) that yields at most the first k items of another async stream. Use it on countdown(10) with k=3.
Write an async context manager timer_label(name) with @asynccontextmanager that prints start <name> and end <name> around a block.
Explain it without notes
What is the difference between a normal generator and an async generator? When do you need the async one?
What does async with do that with cannot, and how can you write your own async context manager?
Why is closing an async generator after break important in streaming code?
How does a bounded asyncio.Queue create back-pressure, and why is task_done called in finally?
You have two async streams (say two model endpoints) and want to handle items from whichever produces first. A plain async for over one then the other does not do that. How do you do it?
Expected output
connection: opening
answer to '2 + 2'
connection: closing, because of no error
job-1: slot taken
job-1: working
job-1: slot released
connection: opening
connection: closing, because of ValueError
caught outside: model returned nonsense