Getting Started
- Unsure How to Get Started? Try our Book Workshop!
- How to Write on Leanpub
- Previewing and publishing
- Basic formatting
- Markdown and Markua
- Generate a preview version of your book
- Either read a tutorial, or just go for it!
- Thanks for being a Leanpub author!
Writing in Markua
- Section One
- Including a Chapter in the Sample Book
- Links
- Images
- Lists
- Page Breaks
- Code Samples
- Tables
- Math
- Headings
- Block quotes, Asides and Blurbs
- Good luck, have fun!
From the Author
Preface
What This Book Is About
What This Book Is Not
Who This Book Is For
How to Use This Book
Conventions Used in This Book
Companion Code and Data
How to Contact Us
Acknowledgments
Contents
The Blocking Function
- 1.1 The first version of
news.py - 1.2 What “blocking” actually means
- 1.3 The cost grows with the list
- 1.4 The same shape, one layer down
- 1.5 The question that drives the rest of the book
- Summary
Concurrency, Parallelism, and the GIL
- 2.1 What a thread is, briefly
- 2.2 The threaded version of
news.py - 2.3 Threading the TCP probes
- 2.4 Why the speedup actually works (and the GIL story you have heard wrong)
- 2.5 Why threads are not the answer the book is reaching for
- 2.6 The two scales the book cares about
- 2.7 Where the next chapter goes
- Summary
The Loop, From Scratch
- 3.1 A queue of jobs
- 3.2 A loop that runs the next ready job
- 3.3 Two senses of “ready”, and the generator that gives us pause
- 3.4 The loop sleeps when nothing is ready
- 3.5 Connecting the toy loop to
news.py - Summary
Coroutines, Without the Magic
- 4.1 Generators, the original coroutine
- 4.2 yield as a pause point, one more time
- 4.3 The toy loop
- 4.4 What async def returns
- Polite driver
for x in gen():(nofor; the loop handles it) - 4.5 Coroutines are not running until the loop runs them
- Summary
async and await
- 5.1 What async does to a function definition
- 5.2 What await does at a call site
- 5.3 What can be awaited, and what cannot
- 5.4 The first real asyncio
news.py - 5.5 The cliffhanger
- 5.6 The same keywords, on raw TCP
- Summary
await Yields, Suspends, and Resumes
- 6.1 What await does
- 6.2 What is saved across the pause
- 6.3 How the loop wakes the coroutine back up
- 6.4 A Task is a coroutine that the loop is driving
- 6.5 For the curious: coro.send(value) and the bytecode
- 6.6 For the curious: selectors and the operating system
- 6.7 Watching news.py yield and resume
- Summary
asyncio.run, create_task, gather
- 7.1 asyncio.run as the starting line
- 7.2 What asyncio.run does
- 7.3 await coro is sequential; create_task(coro) is concurrent
- 7.4 asyncio.gather for “run these together”
- 7.5 The fast
news.py - 7.6 Concurrent TCP probes
- 7.7 The lost-task trap
- Summary
The Time Toolbox
- 8.1 asyncio.sleep yields; time.sleep blocks
- 8.2 wait_for for a deadline around an awaitable
- 8.3 asyncio.timeout() as a context manager (Python 3.11+)
- 8.4 asyncio.shield to protect from cancellation
- 8.5
asyncio.waitvsasyncio.gather - 8.6 asyncio.as_completed for results as they arrive
- 8.7 Timeouts and streaming
- Summary
async for, async with, Async Iteration and Resource Management
- 9.1 async for and the iterator protocol
- 9.2 Async generators
- 9.3
async foroveraiohttpresponse bodies - 9.4 async with and the context-manager protocol
- 9.5 Writing your own async context manager
- 9.6 The streaming
news.py - Summary
Tasks, TaskGroups, Cancellation, Exception Groups, and contextvars
- 10.1
Taskis the loop’s unit of work; the coroutine is just the recipe - 10.2 asyncio.TaskGroup: structured concurrency by construction
- 10.3 Cancellation:
task.cancel()andCancelledErrorat the nextawait - 10.4 The bare-except trap
- 10.5 ExceptionGroup and except*
- 10.6 Re-raising CancelledError after cleanup
- 10.7 Canceling a TCP probe
- 10.8 Synchronization primitives
- 10.9 Capping concurrency with
Semaphore: 50 sites, 10 in flight - 10.10 contextvars for task-scoped state
- 10.11 Producer-consumer with
asyncio.Queue - Summary
What Goes Wrong, and How to Find It
- 11.1 The failure mode: one synchronous call freezes the whole loop
- 11.2 The blocking patterns to learn to recognize
- 11.3 The single most useful tool: asyncio’s debug mode
- 11.4 The four warnings the runtime emits
- 11.5 asyncio.all_tasks() and task.get_stack()
- 11.6 aiomonitor: a live REPL inside a running program
- 11.7 Profiling that sees the loop correctly
- 11.8 Logging task names
- 11.9 pdb and async
- 11.10 A guided debugging session on the broken
news.py - 11.11 tcpcheck.py as a diagnostic tool
- Summary
Testing async code
- 12.1 Your first async test
- 12.2 The standard-library alternative
- 12.3 What mocks are, and why async needs a different one
- 12.4 Testing cancellation
- 12.5 Optional: Testing TaskGroup error propagation
- 12.6 Event loop fixture scope
- 12.7 Testing timeouts without real time
- 12.8 The most subtle async test bug
- 12.9 When a test hangs
- 12.10 The full test suite for the fetcher
- Summary
Where asyncio Ends: Executors, Graceful Shutdown
- 13.1 Fixing the parser bug from Chapter 11
- 13.2 The executor toolbox
- 13.3 The decision tree
- Heavy CPU work that needs to scale across cores Use
multiprocessingdirectly. asyncio is the wrong tool for that workload. - 13.4 Bridging from sync to async, and back
- 13.5 Library design: expose async, do not hide it
- 13.6 Graceful shutdown
- 13.7 The final news.py
- Summary
Appendix A: Setting up your environment
- A.1 Python versions and what they buy you
- A.2 The packages the book uses
- A.3 The smallest runnable asyncio script
- A.4 Reading the running example as you go
Appendix B: The asyncio API surface
- Functions
asyncio.wait_for(coro, timeout)Wraps a single awaitable with a deadline; cancels the inner work and raisesTimeoutErroron expiry. 8- Classes and protocols
ExceptionGroupThe exception type that holds multiple exceptions raised by sibling Tasks in a TaskGroup. Caught withexcept*(3.11+). 10- Loop methods worth knowing
loop.set_default_executor(executor)Replaces the loop’s default executor. Useful when the default thread pool is too small for the workload. 13- aiohttp API surface used in the book
response.text()Awaitable that returns the full response body as a decoded string. 5- Where to find the rest
Appendix C: Troubleshooting guide
- C.1 Asyncio runtime warnings, what they mean and what to fix
RuntimeWarning: async generator 'X' was never closedAn async generator was garbage-collected mid-iteration without the consumer running it to completion or callingaclose(). Wrap resources held betweenyieldintry/finally. Callaclose()explicitly when you need cleanup at a specific moment. (Chapter 9, Section 9.2.1.)- C.2 The script runs without errors but is no faster than synchronous
- Slow-callback warnings fire (
Executing took 0.234 seconds) when running withdebug=True. A coroutine ran for too long betweenawait, blocking the loop. Usually a CPU-bound function or a sync I/O call. Move the slow work to a thread pool withasyncio.to_thread. For CPU-bound work that needs cores, use a process pool vialoop.run_in_executor(ProcessPoolExecutor(), ...). (Chapter 11; Chapter 13.) - C.3 The program hangs or never exits
- Ctrl+C does not interrupt the program. No signal handler installed; or a signal handler that schedules async work but
asyncio.runis blocked elsewhere. Install signal handlers withloop.add_signal_handler(sig, callback). Cancel the top-level Task in the callback. The TaskGroup propagates cancellation to children. (Chapter 13.) - C.4 Common errors and what they mean
RuntimeError: Event loop is closed(often during program shutdown) Code is trying to use the loop afterasyncio.runreturned and closed it. Common when a finalizer or__del__method tries to schedule async work. Move any async cleanup into the running coroutine’s normal exit path (finallyblocks,__aexit__). Do not rely on__del__oratexitfor async cleanup.- When the table does not have your symptom
Appendix D: Further reading
- D.1 The asyncio source code
- D.2 The async library ecosystem
- D.3 The structured concurrency literature
- D.4 The PEPs that shaped asyncio
- D.5 Things this book did not cover, that you may want next
Glossary
About the Author
From the Author
Preface
What This Book Is About
What This Book Is Not
Who This Book Is For
How to Use This Book
Conventions Used in This Book
Companion Code and Data
How to Contact Us
Acknowledgments
Contents
The Blocking Function
- 1.1 The first version of
news.py - 1.2 What “blocking” actually means
- 1.3 The cost grows with the list
- 1.4 The same shape, one layer down
- 1.5 The question that drives the rest of the book
- Summary
Concurrency, Parallelism, and the GIL
- 2.1 What a thread is, briefly
- 2.2 The threaded version of
news.py - 2.3 Threading the TCP probes
- 2.4 Why the speedup actually works (and the GIL story you have heard wrong)
- 2.5 Why threads are not the answer the book is reaching for
- 2.6 The two scales the book cares about
- 2.7 Where the next chapter goes
- Summary
The Loop, From Scratch
- 3.1 A queue of jobs
- 3.2 A loop that runs the next ready job
- 3.3 Two senses of “ready”, and the generator that gives us pause
- 3.4 The loop sleeps when nothing is ready
- 3.5 Connecting the toy loop to
news.py - Summary
Coroutines, Without the Magic
- 4.1 Generators, the original coroutine
- 4.2 yield as a pause point, one more time
- 4.3 The toy loop
- 4.4 What async def returns
- Polite driver
for x in gen():(nofor; the loop handles it) - 4.5 Coroutines are not running until the loop runs them
- Summary
async and await
- 5.1 What async does to a function definition
- 5.2 What await does at a call site
- 5.3 What can be awaited, and what cannot
- 5.4 The first real asyncio
news.py - 5.5 The cliffhanger
- 5.6 The same keywords, on raw TCP
- Summary
await Yields, Suspends, and Resumes
- 6.1 What await does
- 6.2 What is saved across the pause
- 6.3 How the loop wakes the coroutine back up
- 6.4 A Task is a coroutine that the loop is driving
- 6.5 For the curious: coro.send(value) and the bytecode
- 6.6 For the curious: selectors and the operating system
- 6.7 Watching news.py yield and resume
- Summary
asyncio.run, create_task, gather
- 7.1 asyncio.run as the starting line
- 7.2 What asyncio.run does
- 7.3 await coro is sequential; create_task(coro) is concurrent
- 7.4 asyncio.gather for “run these together”
- 7.5 The fast
news.py - 7.6 Concurrent TCP probes
- 7.7 The lost-task trap
- Summary
The Time Toolbox
- 8.1 asyncio.sleep yields; time.sleep blocks
- 8.2 wait_for for a deadline around an awaitable
- 8.3 asyncio.timeout() as a context manager (Python 3.11+)
- 8.4 asyncio.shield to protect from cancellation
- 8.5
asyncio.waitvsasyncio.gather - 8.6 asyncio.as_completed for results as they arrive
- 8.7 Timeouts and streaming
- Summary
async for, async with, Async Iteration and Resource Management
- 9.1 async for and the iterator protocol
- 9.2 Async generators
- 9.3
async foroveraiohttpresponse bodies - 9.4 async with and the context-manager protocol
- 9.5 Writing your own async context manager
- 9.6 The streaming
news.py - Summary
Tasks, TaskGroups, Cancellation, Exception Groups, and contextvars
- 10.1
Taskis the loop’s unit of work; the coroutine is just the recipe - 10.2 asyncio.TaskGroup: structured concurrency by construction
- 10.3 Cancellation:
task.cancel()andCancelledErrorat the nextawait - 10.4 The bare-except trap
- 10.5 ExceptionGroup and except*
- 10.6 Re-raising CancelledError after cleanup
- 10.7 Canceling a TCP probe
- 10.8 Synchronization primitives
- 10.9 Capping concurrency with
Semaphore: 50 sites, 10 in flight - 10.10 contextvars for task-scoped state
- 10.11 Producer-consumer with
asyncio.Queue - Summary
What Goes Wrong, and How to Find It
- 11.1 The failure mode: one synchronous call freezes the whole loop
- 11.2 The blocking patterns to learn to recognize
- 11.3 The single most useful tool: asyncio’s debug mode
- 11.4 The four warnings the runtime emits
- 11.5 asyncio.all_tasks() and task.get_stack()
- 11.6 aiomonitor: a live REPL inside a running program
- 11.7 Profiling that sees the loop correctly
- 11.8 Logging task names
- 11.9 pdb and async
- 11.10 A guided debugging session on the broken
news.py - 11.11 tcpcheck.py as a diagnostic tool
- Summary
Testing async code
- 12.1 Your first async test
- 12.2 The standard-library alternative
- 12.3 What mocks are, and why async needs a different one
- 12.4 Testing cancellation
- 12.5 Optional: Testing TaskGroup error propagation
- 12.6 Event loop fixture scope
- 12.7 Testing timeouts without real time
- 12.8 The most subtle async test bug
- 12.9 When a test hangs
- 12.10 The full test suite for the fetcher
- Summary
Where asyncio Ends: Executors, Graceful Shutdown
- 13.1 Fixing the parser bug from Chapter 11
- 13.2 The executor toolbox
- 13.3 The decision tree
- Heavy CPU work that needs to scale across cores Use
multiprocessingdirectly. asyncio is the wrong tool for that workload. - 13.4 Bridging from sync to async, and back
- 13.5 Library design: expose async, do not hide it
- 13.6 Graceful shutdown
- 13.7 The final news.py
- Summary
Appendix A: Setting up your environment
- A.1 Python versions and what they buy you
- A.2 The packages the book uses
- A.3 The smallest runnable asyncio script
- A.4 Reading the running example as you go
Appendix B: The asyncio API surface
- Functions
asyncio.wait_for(coro, timeout)Wraps a single awaitable with a deadline; cancels the inner work and raisesTimeoutErroron expiry. 8- Classes and protocols
ExceptionGroupThe exception type that holds multiple exceptions raised by sibling Tasks in a TaskGroup. Caught withexcept*(3.11+). 10- Loop methods worth knowing
loop.set_default_executor(executor)Replaces the loop’s default executor. Useful when the default thread pool is too small for the workload. 13- aiohttp API surface used in the book
response.text()Awaitable that returns the full response body as a decoded string. 5- Where to find the rest
Appendix C: Troubleshooting guide
- C.1 Asyncio runtime warnings, what they mean and what to fix
RuntimeWarning: async generator 'X' was never closedAn async generator was garbage-collected mid-iteration without the consumer running it to completion or callingaclose(). Wrap resources held betweenyieldintry/finally. Callaclose()explicitly when you need cleanup at a specific moment. (Chapter 9, Section 9.2.1.)- C.2 The script runs without errors but is no faster than synchronous
- Slow-callback warnings fire (
Executing took 0.234 seconds) when running withdebug=True. A coroutine ran for too long betweenawait, blocking the loop. Usually a CPU-bound function or a sync I/O call. Move the slow work to a thread pool withasyncio.to_thread. For CPU-bound work that needs cores, use a process pool vialoop.run_in_executor(ProcessPoolExecutor(), ...). (Chapter 11; Chapter 13.) - C.3 The program hangs or never exits
- Ctrl+C does not interrupt the program. No signal handler installed; or a signal handler that schedules async work but
asyncio.runis blocked elsewhere. Install signal handlers withloop.add_signal_handler(sig, callback). Cancel the top-level Task in the callback. The TaskGroup propagates cancellation to children. (Chapter 13.) - C.4 Common errors and what they mean
RuntimeError: Event loop is closed(often during program shutdown) Code is trying to use the loop afterasyncio.runreturned and closed it. Common when a finalizer or__del__method tries to schedule async work. Move any async cleanup into the running coroutine’s normal exit path (finallyblocks,__aexit__). Do not rely on__del__oratexitfor async cleanup.- When the table does not have your symptom
Appendix D: Further reading
- D.1 The asyncio source code
- D.2 The async library ecosystem
- D.3 The structured concurrency literature
- D.4 The PEPs that shaped asyncio
- D.5 Things this book did not cover, that you may want next
Glossary
About the Author
From the Author
Preface
What This Book Is About
What This Book Is Not
Who This Book Is For
How to Use This Book
Conventions Used in This Book
Companion Code and Data
How to Contact Us
Acknowledgments
Contents
The Blocking Function
- 1.1 The first version of
news.py - 1.2 What “blocking” actually means
- 1.3 The cost grows with the list
- 1.4 The same shape, one layer down
- 1.5 The question that drives the rest of the book
- Summary
Concurrency, Parallelism, and the GIL
- 2.1 What a thread is, briefly
- 2.2 The threaded version of
news.py - 2.3 Threading the TCP probes
- 2.4 Why the speedup actually works (and the GIL story you have heard wrong)
- 2.5 Why threads are not the answer the book is reaching for
- 2.6 The two scales the book cares about
- 2.7 Where the next chapter goes
- Summary
The Loop, From Scratch
- 3.1 A queue of jobs
- 3.2 A loop that runs the next ready job
- 3.3 Two senses of “ready”, and the generator that gives us pause
- 3.4 The loop sleeps when nothing is ready
- 3.5 Connecting the toy loop to
news.py - Summary
Coroutines, Without the Magic
- 4.1 Generators, the original coroutine
- 4.2 yield as a pause point, one more time
- 4.3 The toy loop
- 4.4 What async def returns
- Polite driver
for x in gen():(nofor; the loop handles it) - 4.5 Coroutines are not running until the loop runs them
- Summary
async and await
- 5.1 What async does to a function definition
- 5.2 What await does at a call site
- 5.3 What can be awaited, and what cannot
- 5.4 The first real asyncio
news.py - 5.5 The cliffhanger
- 5.6 The same keywords, on raw TCP
- Summary
await Yields, Suspends, and Resumes
- 6.1 What await does
- 6.2 What is saved across the pause
- 6.3 How the loop wakes the coroutine back up
- 6.4 A Task is a coroutine that the loop is driving
- 6.5 For the curious: coro.send(value) and the bytecode
- 6.6 For the curious: selectors and the operating system
- 6.7 Watching news.py yield and resume
- Summary
asyncio.run, create_task, gather
- 7.1 asyncio.run as the starting line
- 7.2 What asyncio.run does
- 7.3 await coro is sequential; create_task(coro) is concurrent
- 7.4 asyncio.gather for “run these together”
- 7.5 The fast
news.py - 7.6 Concurrent TCP probes
- 7.7 The lost-task trap
- Summary
The Time Toolbox
- 8.1 asyncio.sleep yields; time.sleep blocks
- 8.2 wait_for for a deadline around an awaitable
- 8.3 asyncio.timeout() as a context manager (Python 3.11+)
- 8.4 asyncio.shield to protect from cancellation
- 8.5
asyncio.waitvsasyncio.gather - 8.6 asyncio.as_completed for results as they arrive
- 8.7 Timeouts and streaming
- Summary
async for, async with, Async Iteration and Resource Management
- 9.1 async for and the iterator protocol
- 9.2 Async generators
- 9.3
async foroveraiohttpresponse bodies - 9.4 async with and the context-manager protocol
- 9.5 Writing your own async context manager
- 9.6 The streaming
news.py - Summary
Tasks, TaskGroups, Cancellation, Exception Groups, and contextvars
- 10.1
Taskis the loop’s unit of work; the coroutine is just the recipe - 10.2 asyncio.TaskGroup: structured concurrency by construction
- 10.3 Cancellation:
task.cancel()andCancelledErrorat the nextawait - 10.4 The bare-except trap
- 10.5 ExceptionGroup and except*
- 10.6 Re-raising CancelledError after cleanup
- 10.7 Canceling a TCP probe
- 10.8 Synchronization primitives
- 10.9 Capping concurrency with
Semaphore: 50 sites, 10 in flight - 10.10 contextvars for task-scoped state
- 10.11 Producer-consumer with
asyncio.Queue - Summary
What Goes Wrong, and How to Find It
- 11.1 The failure mode: one synchronous call freezes the whole loop
- 11.2 The blocking patterns to learn to recognize
- 11.3 The single most useful tool: asyncio’s debug mode
- 11.4 The four warnings the runtime emits
- 11.5 asyncio.all_tasks() and task.get_stack()
- 11.6 aiomonitor: a live REPL inside a running program
- 11.7 Profiling that sees the loop correctly
- 11.8 Logging task names
- 11.9 pdb and async
- 11.10 A guided debugging session on the broken
news.py - 11.11 tcpcheck.py as a diagnostic tool
- Summary
Testing async code
- 12.1 Your first async test
- 12.2 The standard-library alternative
- 12.3 What mocks are, and why async needs a different one
- 12.4 Testing cancellation
- 12.5 Optional: Testing TaskGroup error propagation
- 12.6 Event loop fixture scope
- 12.7 Testing timeouts without real time
- 12.8 The most subtle async test bug
- 12.9 When a test hangs
- 12.10 The full test suite for the fetcher
- Summary
Where asyncio Ends: Executors, Graceful Shutdown
- 13.1 Fixing the parser bug from Chapter 11
- 13.2 The executor toolbox
- 13.3 The decision tree
- Heavy CPU work that needs to scale across cores Use
multiprocessingdirectly. asyncio is the wrong tool for that workload. - 13.4 Bridging from sync to async, and back
- 13.5 Library design: expose async, do not hide it
- 13.6 Graceful shutdown
- 13.7 The final news.py
- Summary
Appendix A: Setting up your environment
- A.1 Python versions and what they buy you
- A.2 The packages the book uses
- A.3 The smallest runnable asyncio script
- A.4 Reading the running example as you go
Appendix B: The asyncio API surface
- Functions
asyncio.wait_for(coro, timeout)Wraps a single awaitable with a deadline; cancels the inner work and raisesTimeoutErroron expiry. 8- Classes and protocols
ExceptionGroupThe exception type that holds multiple exceptions raised by sibling Tasks in a TaskGroup. Caught withexcept*(3.11+). 10- Loop methods worth knowing
loop.set_default_executor(executor)Replaces the loop’s default executor. Useful when the default thread pool is too small for the workload. 13- aiohttp API surface used in the book
response.text()Awaitable that returns the full response body as a decoded string. 5- Where to find the rest
Appendix C: Troubleshooting guide
- C.1 Asyncio runtime warnings, what they mean and what to fix
RuntimeWarning: async generator 'X' was never closedAn async generator was garbage-collected mid-iteration without the consumer running it to completion or callingaclose(). Wrap resources held betweenyieldintry/finally. Callaclose()explicitly when you need cleanup at a specific moment. (Chapter 9, Section 9.2.1.)- C.2 The script runs without errors but is no faster than synchronous
- Slow-callback warnings fire (
Executing took 0.234 seconds) when running withdebug=True. A coroutine ran for too long betweenawait, blocking the loop. Usually a CPU-bound function or a sync I/O call. Move the slow work to a thread pool withasyncio.to_thread. For CPU-bound work that needs cores, use a process pool vialoop.run_in_executor(ProcessPoolExecutor(), ...). (Chapter 11; Chapter 13.) - C.3 The program hangs or never exits
- Ctrl+C does not interrupt the program. No signal handler installed; or a signal handler that schedules async work but
asyncio.runis blocked elsewhere. Install signal handlers withloop.add_signal_handler(sig, callback). Cancel the top-level Task in the callback. The TaskGroup propagates cancellation to children. (Chapter 13.) - C.4 Common errors and what they mean
RuntimeError: Event loop is closed(often during program shutdown) Code is trying to use the loop afterasyncio.runreturned and closed it. Common when a finalizer or__del__method tries to schedule async work. Move any async cleanup into the running coroutine’s normal exit path (finallyblocks,__aexit__). Do not rely on__del__oratexitfor async cleanup.- When the table does not have your symptom
Appendix D: Further reading
- D.1 The asyncio source code
- D.2 The async library ecosystem
- D.3 The structured concurrency literature
- D.4 The PEPs that shaped asyncio
- D.5 Things this book did not cover, that you may want next
Glossary
About the Author
From the Author
Preface
What This Book Is About
What This Book Is Not
Who This Book Is For
How to Use This Book
Conventions Used in This Book
Companion Code and Data
How to Contact Us
Acknowledgments
Contents
The Blocking Function
- 1.1 The first version of
news.py - 1.2 What “blocking” actually means
- 1.3 The cost grows with the list
- 1.4 The same shape, one layer down
- 1.5 The question that drives the rest of the book
- Summary
Concurrency, Parallelism, and the GIL
- 2.1 What a thread is, briefly
- 2.2 The threaded version of
news.py - 2.3 Threading the TCP probes
- 2.4 Why the speedup actually works (and the GIL story you have heard wrong)
- 2.5 Why threads are not the answer the book is reaching for
- 2.6 The two scales the book cares about
- 2.7 Where the next chapter goes
- Summary
The Loop, From Scratch
- 3.1 A queue of jobs
- 3.2 A loop that runs the next ready job
- 3.3 Two senses of “ready”, and the generator that gives us pause
- 3.4 The loop sleeps when nothing is ready
- 3.5 Connecting the toy loop to
news.py - Summary
Coroutines, Without the Magic
- 4.1 Generators, the original coroutine
- 4.2 yield as a pause point, one more time
- 4.3 The toy loop
- 4.4 What async def returns
- Polite driver
for x in gen():(nofor; the loop handles it) - 4.5 Coroutines are not running until the loop runs them
- Summary
async and await
- 5.1 What async does to a function definition
- 5.2 What await does at a call site
- 5.3 What can be awaited, and what cannot
- 5.4 The first real asyncio
news.py - 5.5 The cliffhanger
- 5.6 The same keywords, on raw TCP
- Summary
await Yields, Suspends, and Resumes
- 6.1 What await does
- 6.2 What is saved across the pause
- 6.3 How the loop wakes the coroutine back up
- 6.4 A Task is a coroutine that the loop is driving
- 6.5 For the curious: coro.send(value) and the bytecode
- 6.6 For the curious: selectors and the operating system
- 6.7 Watching news.py yield and resume
- Summary
asyncio.run, create_task, gather
- 7.1 asyncio.run as the starting line
- 7.2 What asyncio.run does
- 7.3 await coro is sequential; create_task(coro) is concurrent
- 7.4 asyncio.gather for “run these together”
- 7.5 The fast
news.py - 7.6 Concurrent TCP probes
- 7.7 The lost-task trap
- Summary
The Time Toolbox
- 8.1 asyncio.sleep yields; time.sleep blocks
- 8.2 wait_for for a deadline around an awaitable
- 8.3 asyncio.timeout() as a context manager (Python 3.11+)
- 8.4 asyncio.shield to protect from cancellation
- 8.5
asyncio.waitvsasyncio.gather - 8.6 asyncio.as_completed for results as they arrive
- 8.7 Timeouts and streaming
- Summary
async for, async with, Async Iteration and Resource Management
- 9.1 async for and the iterator protocol
- 9.2 Async generators
- 9.3
async foroveraiohttpresponse bodies - 9.4 async with and the context-manager protocol
- 9.5 Writing your own async context manager
- 9.6 The streaming
news.py - Summary
Tasks, TaskGroups, Cancellation, Exception Groups, and contextvars
- 10.1
Taskis the loop’s unit of work; the coroutine is just the recipe - 10.2 asyncio.TaskGroup: structured concurrency by construction
- 10.3 Cancellation:
task.cancel()andCancelledErrorat the nextawait - 10.4 The bare-except trap
- 10.5 ExceptionGroup and except*
- 10.6 Re-raising CancelledError after cleanup
- 10.7 Canceling a TCP probe
- 10.8 Synchronization primitives
- 10.9 Capping concurrency with
Semaphore: 50 sites, 10 in flight - 10.10 contextvars for task-scoped state
- 10.11 Producer-consumer with
asyncio.Queue - Summary
What Goes Wrong, and How to Find It
- 11.1 The failure mode: one synchronous call freezes the whole loop
- 11.2 The blocking patterns to learn to recognize
- 11.3 The single most useful tool: asyncio’s debug mode
- 11.4 The four warnings the runtime emits
- 11.5 asyncio.all_tasks() and task.get_stack()
- 11.6 aiomonitor: a live REPL inside a running program
- 11.7 Profiling that sees the loop correctly
- 11.8 Logging task names
- 11.9 pdb and async
- 11.10 A guided debugging session on the broken
news.py - 11.11 tcpcheck.py as a diagnostic tool
- Summary
Testing async code
- 12.1 Your first async test
- 12.2 The standard-library alternative
- 12.3 What mocks are, and why async needs a different one
- 12.4 Testing cancellation
- 12.5 Optional: Testing TaskGroup error propagation
- 12.6 Event loop fixture scope
- 12.7 Testing timeouts without real time
- 12.8 The most subtle async test bug
- 12.9 When a test hangs
- 12.10 The full test suite for the fetcher
- Summary
Where asyncio Ends: Executors, Graceful Shutdown
- 13.1 Fixing the parser bug from Chapter 11
- 13.2 The executor toolbox
- 13.3 The decision tree
- Heavy CPU work that needs to scale across cores Use
multiprocessingdirectly. asyncio is the wrong tool for that workload. - 13.4 Bridging from sync to async, and back
- 13.5 Library design: expose async, do not hide it
- 13.6 Graceful shutdown
- 13.7 The final news.py
- Summary
Appendix A: Setting up your environment
- A.1 Python versions and what they buy you
- A.2 The packages the book uses
- A.3 The smallest runnable asyncio script
- A.4 Reading the running example as you go
Appendix B: The asyncio API surface
- Functions
asyncio.wait_for(coro, timeout)Wraps a single awaitable with a deadline; cancels the inner work and raisesTimeoutErroron expiry. 8- Classes and protocols
ExceptionGroupThe exception type that holds multiple exceptions raised by sibling Tasks in a TaskGroup. Caught withexcept*(3.11+). 10- Loop methods worth knowing
loop.set_default_executor(executor)Replaces the loop’s default executor. Useful when the default thread pool is too small for the workload. 13- aiohttp API surface used in the book
response.text()Awaitable that returns the full response body as a decoded string. 5- Where to find the rest
Appendix C: Troubleshooting guide
- C.1 Asyncio runtime warnings, what they mean and what to fix
RuntimeWarning: async generator 'X' was never closedAn async generator was garbage-collected mid-iteration without the consumer running it to completion or callingaclose(). Wrap resources held betweenyieldintry/finally. Callaclose()explicitly when you need cleanup at a specific moment. (Chapter 9, Section 9.2.1.)- C.2 The script runs without errors but is no faster than synchronous
- Slow-callback warnings fire (
Executing took 0.234 seconds) when running withdebug=True. A coroutine ran for too long betweenawait, blocking the loop. Usually a CPU-bound function or a sync I/O call. Move the slow work to a thread pool withasyncio.to_thread. For CPU-bound work that needs cores, use a process pool vialoop.run_in_executor(ProcessPoolExecutor(), ...). (Chapter 11; Chapter 13.) - C.3 The program hangs or never exits
- Ctrl+C does not interrupt the program. No signal handler installed; or a signal handler that schedules async work but
asyncio.runis blocked elsewhere. Install signal handlers withloop.add_signal_handler(sig, callback). Cancel the top-level Task in the callback. The TaskGroup propagates cancellation to children. (Chapter 13.) - C.4 Common errors and what they mean
RuntimeError: Event loop is closed(often during program shutdown) Code is trying to use the loop afterasyncio.runreturned and closed it. Common when a finalizer or__del__method tries to schedule async work. Move any async cleanup into the running coroutine’s normal exit path (finallyblocks,__aexit__). Do not rely on__del__oratexitfor async cleanup.- When the table does not have your symptom
Appendix D: Further reading
- D.1 The asyncio source code
- D.2 The async library ecosystem
- D.3 The structured concurrency literature
- D.4 The PEPs that shaped asyncio
- D.5 Things this book did not cover, that you may want next
Glossary
About the Author
From the Author
Preface
What This Book Is About
What This Book Is Not
Who This Book Is For
How to Use This Book
Conventions Used in This Book
Companion Code and Data
How to Contact Us
Acknowledgments
Contents
The Blocking Function
- 1.1 The first version of
news.py - 1.2 What “blocking” actually means
- 1.3 The cost grows with the list
- 1.4 The same shape, one layer down
- 1.5 The question that drives the rest of the book
- Summary
Concurrency, Parallelism, and the GIL
- 2.1 What a thread is, briefly
- 2.2 The threaded version of
news.py - 2.3 Threading the TCP probes
- 2.4 Why the speedup actually works (and the GIL story you have heard wrong)
- 2.5 Why threads are not the answer the book is reaching for
- 2.6 The two scales the book cares about
- 2.7 Where the next chapter goes
- Summary
The Loop, From Scratch
- 3.1 A queue of jobs
- 3.2 A loop that runs the next ready job
- 3.3 Two senses of “ready”, and the generator that gives us pause
- 3.4 The loop sleeps when nothing is ready
- 3.5 Connecting the toy loop to
news.py - Summary
Coroutines, Without the Magic
- 4.1 Generators, the original coroutine
- 4.2 yield as a pause point, one more time
- 4.3 The toy loop
- 4.4 What async def returns
- Polite driver
for x in gen():(nofor; the loop handles it) - 4.5 Coroutines are not running until the loop runs them
- Summary
async and await
- 5.1 What async does to a function definition
- 5.2 What await does at a call site
- 5.3 What can be awaited, and what cannot
- 5.4 The first real asyncio
news.py - 5.5 The cliffhanger
- 5.6 The same keywords, on raw TCP
- Summary
await Yields, Suspends, and Resumes
- 6.1 What await does
- 6.2 What is saved across the pause
- 6.3 How the loop wakes the coroutine back up
- 6.4 A Task is a coroutine that the loop is driving
- 6.5 For the curious: coro.send(value) and the bytecode
- 6.6 For the curious: selectors and the operating system
- 6.7 Watching news.py yield and resume
- Summary
asyncio.run, create_task, gather
- 7.1 asyncio.run as the starting line
- 7.2 What asyncio.run does
- 7.3 await coro is sequential; create_task(coro) is concurrent
- 7.4 asyncio.gather for “run these together”
- 7.5 The fast
news.py - 7.6 Concurrent TCP probes
- 7.7 The lost-task trap
- Summary
The Time Toolbox
- 8.1 asyncio.sleep yields; time.sleep blocks
- 8.2 wait_for for a deadline around an awaitable
- 8.3 asyncio.timeout() as a context manager (Python 3.11+)
- 8.4 asyncio.shield to protect from cancellation
- 8.5
asyncio.waitvsasyncio.gather - 8.6 asyncio.as_completed for results as they arrive
- 8.7 Timeouts and streaming
- Summary
async for, async with, Async Iteration and Resource Management
- 9.1 async for and the iterator protocol
- 9.2 Async generators
- 9.3
async foroveraiohttpresponse bodies - 9.4 async with and the context-manager protocol
- 9.5 Writing your own async context manager
- 9.6 The streaming
news.py - Summary
Tasks, TaskGroups, Cancellation, Exception Groups, and contextvars
- 10.1
Taskis the loop’s unit of work; the coroutine is just the recipe - 10.2 asyncio.TaskGroup: structured concurrency by construction
- 10.3 Cancellation:
task.cancel()andCancelledErrorat the nextawait - 10.4 The bare-except trap
- 10.5 ExceptionGroup and except*
- 10.6 Re-raising CancelledError after cleanup
- 10.7 Canceling a TCP probe
- 10.8 Synchronization primitives
- 10.9 Capping concurrency with
Semaphore: 50 sites, 10 in flight - 10.10 contextvars for task-scoped state
- 10.11 Producer-consumer with
asyncio.Queue - Summary
What Goes Wrong, and How to Find It
- 11.1 The failure mode: one synchronous call freezes the whole loop
- 11.2 The blocking patterns to learn to recognize
- 11.3 The single most useful tool: asyncio’s debug mode
- 11.4 The four warnings the runtime emits
- 11.5 asyncio.all_tasks() and task.get_stack()
- 11.6 aiomonitor: a live REPL inside a running program
- 11.7 Profiling that sees the loop correctly
- 11.8 Logging task names
- 11.9 pdb and async
- 11.10 A guided debugging session on the broken
news.py - 11.11 tcpcheck.py as a diagnostic tool
- Summary
Testing async code
- 12.1 Your first async test
- 12.2 The standard-library alternative
- 12.3 What mocks are, and why async needs a different one
- 12.4 Testing cancellation
- 12.5 Optional: Testing TaskGroup error propagation
- 12.6 Event loop fixture scope
- 12.7 Testing timeouts without real time
- 12.8 The most subtle async test bug
- 12.9 When a test hangs
- 12.10 The full test suite for the fetcher
- Summary
Where asyncio Ends: Executors, Graceful Shutdown
- 13.1 Fixing the parser bug from Chapter 11
- 13.2 The executor toolbox
- 13.3 The decision tree
- Heavy CPU work that needs to scale across cores Use
multiprocessingdirectly. asyncio is the wrong tool for that workload. - 13.4 Bridging from sync to async, and back
- 13.5 Library design: expose async, do not hide it
- 13.6 Graceful shutdown
- 13.7 The final news.py
- Summary
Appendix A: Setting up your environment
- A.1 Python versions and what they buy you
- A.2 The packages the book uses
- A.3 The smallest runnable asyncio script
- A.4 Reading the running example as you go
Appendix B: The asyncio API surface
- Functions
asyncio.wait_for(coro, timeout)Wraps a single awaitable with a deadline; cancels the inner work and raisesTimeoutErroron expiry. 8- Classes and protocols
ExceptionGroupThe exception type that holds multiple exceptions raised by sibling Tasks in a TaskGroup. Caught withexcept*(3.11+). 10- Loop methods worth knowing
loop.set_default_executor(executor)Replaces the loop’s default executor. Useful when the default thread pool is too small for the workload. 13- aiohttp API surface used in the book
response.text()Awaitable that returns the full response body as a decoded string. 5- Where to find the rest
Appendix C: Troubleshooting guide
- C.1 Asyncio runtime warnings, what they mean and what to fix
RuntimeWarning: async generator 'X' was never closedAn async generator was garbage-collected mid-iteration without the consumer running it to completion or callingaclose(). Wrap resources held betweenyieldintry/finally. Callaclose()explicitly when you need cleanup at a specific moment. (Chapter 9, Section 9.2.1.)- C.2 The script runs without errors but is no faster than synchronous
- Slow-callback warnings fire (
Executing took 0.234 seconds) when running withdebug=True. A coroutine ran for too long betweenawait, blocking the loop. Usually a CPU-bound function or a sync I/O call. Move the slow work to a thread pool withasyncio.to_thread. For CPU-bound work that needs cores, use a process pool vialoop.run_in_executor(ProcessPoolExecutor(), ...). (Chapter 11; Chapter 13.) - C.3 The program hangs or never exits
- Ctrl+C does not interrupt the program. No signal handler installed; or a signal handler that schedules async work but
asyncio.runis blocked elsewhere. Install signal handlers withloop.add_signal_handler(sig, callback). Cancel the top-level Task in the callback. The TaskGroup propagates cancellation to children. (Chapter 13.) - C.4 Common errors and what they mean
RuntimeError: Event loop is closed(often during program shutdown) Code is trying to use the loop afterasyncio.runreturned and closed it. Common when a finalizer or__del__method tries to schedule async work. Move any async cleanup into the running coroutine’s normal exit path (finallyblocks,__aexit__). Do not rely on__del__oratexitfor async cleanup.- When the table does not have your symptom
Appendix D: Further reading
- D.1 The asyncio source code
- D.2 The async library ecosystem
- D.3 The structured concurrency literature
- D.4 The PEPs that shaped asyncio
- D.5 Things this book did not cover, that you may want next
Glossary
About the Author
From the Author
Preface
What This Book Is About
What This Book Is Not
Who This Book Is For
How to Use This Book
Conventions Used in This Book
Companion Code and Data
How to Contact Us
Acknowledgments
Contents
The Blocking Function
- 1.1 The first version of
news.py - 1.2 What “blocking” actually means
- 1.3 The cost grows with the list
- 1.4 The same shape, one layer down
- 1.5 The question that drives the rest of the book
- Summary
Concurrency, Parallelism, and the GIL
- 2.1 What a thread is, briefly
- 2.2 The threaded version of
news.py - 2.3 Threading the TCP probes
- 2.4 Why the speedup actually works (and the GIL story you have heard wrong)
- 2.5 Why threads are not the answer the book is reaching for
- 2.6 The two scales the book cares about
- 2.7 Where the next chapter goes
- Summary
The Loop, From Scratch
- 3.1 A queue of jobs
- 3.2 A loop that runs the next ready job
- 3.3 Two senses of “ready”, and the generator that gives us pause
- 3.4 The loop sleeps when nothing is ready
- 3.5 Connecting the toy loop to
news.py - Summary
Coroutines, Without the Magic
- 4.1 Generators, the original coroutine
- 4.2 yield as a pause point, one more time
- 4.3 The toy loop
- 4.4 What async def returns
- Polite driver
for x in gen():(nofor; the loop handles it) - 4.5 Coroutines are not running until the loop runs them
- Summary
async and await
- 5.1 What async does to a function definition
- 5.2 What await does at a call site
- 5.3 What can be awaited, and what cannot
- 5.4 The first real asyncio
news.py - 5.5 The cliffhanger
- 5.6 The same keywords, on raw TCP
- Summary
await Yields, Suspends, and Resumes
- 6.1 What await does
- 6.2 What is saved across the pause
- 6.3 How the loop wakes the coroutine back up
- 6.4 A Task is a coroutine that the loop is driving
- 6.5 For the curious: coro.send(value) and the bytecode
- 6.6 For the curious: selectors and the operating system
- 6.7 Watching news.py yield and resume
- Summary
asyncio.run, create_task, gather
- 7.1 asyncio.run as the starting line
- 7.2 What asyncio.run does
- 7.3 await coro is sequential; create_task(coro) is concurrent
- 7.4 asyncio.gather for “run these together”
- 7.5 The fast
news.py - 7.6 Concurrent TCP probes
- 7.7 The lost-task trap
- Summary
The Time Toolbox
- 8.1 asyncio.sleep yields; time.sleep blocks
- 8.2 wait_for for a deadline around an awaitable
- 8.3 asyncio.timeout() as a context manager (Python 3.11+)
- 8.4 asyncio.shield to protect from cancellation
- 8.5
asyncio.waitvsasyncio.gather - 8.6 asyncio.as_completed for results as they arrive
- 8.7 Timeouts and streaming
- Summary
async for, async with, Async Iteration and Resource Management
- 9.1 async for and the iterator protocol
- 9.2 Async generators
- 9.3
async foroveraiohttpresponse bodies - 9.4 async with and the context-manager protocol
- 9.5 Writing your own async context manager
- 9.6 The streaming
news.py - Summary
Tasks, TaskGroups, Cancellation, Exception Groups, and contextvars
- 10.1
Taskis the loop’s unit of work; the coroutine is just the recipe - 10.2 asyncio.TaskGroup: structured concurrency by construction
- 10.3 Cancellation:
task.cancel()andCancelledErrorat the nextawait - 10.4 The bare-except trap
- 10.5 ExceptionGroup and except*
- 10.6 Re-raising CancelledError after cleanup
- 10.7 Canceling a TCP probe
- 10.8 Synchronization primitives
- 10.9 Capping concurrency with
Semaphore: 50 sites, 10 in flight - 10.10 contextvars for task-scoped state
- 10.11 Producer-consumer with
asyncio.Queue - Summary
What Goes Wrong, and How to Find It
- 11.1 The failure mode: one synchronous call freezes the whole loop
- 11.2 The blocking patterns to learn to recognize
- 11.3 The single most useful tool: asyncio’s debug mode
- 11.4 The four warnings the runtime emits
- 11.5 asyncio.all_tasks() and task.get_stack()
- 11.6 aiomonitor: a live REPL inside a running program
- 11.7 Profiling that sees the loop correctly
- 11.8 Logging task names
- 11.9 pdb and async
- 11.10 A guided debugging session on the broken
news.py - 11.11 tcpcheck.py as a diagnostic tool
- Summary
Testing async code
- 12.1 Your first async test
- 12.2 The standard-library alternative
- 12.3 What mocks are, and why async needs a different one
- 12.4 Testing cancellation
- 12.5 Optional: Testing TaskGroup error propagation
- 12.6 Event loop fixture scope
- 12.7 Testing timeouts without real time
- 12.8 The most subtle async test bug
- 12.9 When a test hangs
- 12.10 The full test suite for the fetcher
- Summary
Where asyncio Ends: Executors, Graceful Shutdown
- 13.1 Fixing the parser bug from Chapter 11
- 13.2 The executor toolbox
- 13.3 The decision tree
- Heavy CPU work that needs to scale across cores Use
multiprocessingdirectly. asyncio is the wrong tool for that workload. - 13.4 Bridging from sync to async, and back
- 13.5 Library design: expose async, do not hide it
- 13.6 Graceful shutdown
- 13.7 The final news.py
- Summary
Appendix A: Setting up your environment
- A.1 Python versions and what they buy you
- A.2 The packages the book uses
- A.3 The smallest runnable asyncio script
- A.4 Reading the running example as you go
Appendix B: The asyncio API surface
- Functions
asyncio.wait_for(coro, timeout)Wraps a single awaitable with a deadline; cancels the inner work and raisesTimeoutErroron expiry. 8- Classes and protocols
ExceptionGroupThe exception type that holds multiple exceptions raised by sibling Tasks in a TaskGroup. Caught withexcept*(3.11+). 10- Loop methods worth knowing
loop.set_default_executor(executor)Replaces the loop’s default executor. Useful when the default thread pool is too small for the workload. 13- aiohttp API surface used in the book
response.text()Awaitable that returns the full response body as a decoded string. 5- Where to find the rest
Appendix C: Troubleshooting guide
- C.1 Asyncio runtime warnings, what they mean and what to fix
RuntimeWarning: async generator 'X' was never closedAn async generator was garbage-collected mid-iteration without the consumer running it to completion or callingaclose(). Wrap resources held betweenyieldintry/finally. Callaclose()explicitly when you need cleanup at a specific moment. (Chapter 9, Section 9.2.1.)- C.2 The script runs without errors but is no faster than synchronous
- Slow-callback warnings fire (
Executing took 0.234 seconds) when running withdebug=True. A coroutine ran for too long betweenawait, blocking the loop. Usually a CPU-bound function or a sync I/O call. Move the slow work to a thread pool withasyncio.to_thread. For CPU-bound work that needs cores, use a process pool vialoop.run_in_executor(ProcessPoolExecutor(), ...). (Chapter 11; Chapter 13.) - C.3 The program hangs or never exits
- Ctrl+C does not interrupt the program. No signal handler installed; or a signal handler that schedules async work but
asyncio.runis blocked elsewhere. Install signal handlers withloop.add_signal_handler(sig, callback). Cancel the top-level Task in the callback. The TaskGroup propagates cancellation to children. (Chapter 13.) - C.4 Common errors and what they mean
RuntimeError: Event loop is closed(often during program shutdown) Code is trying to use the loop afterasyncio.runreturned and closed it. Common when a finalizer or__del__method tries to schedule async work. Move any async cleanup into the running coroutine’s normal exit path (finallyblocks,__aexit__). Do not rely on__del__oratexitfor async cleanup.- When the table does not have your symptom
Appendix D: Further reading
- D.1 The asyncio source code
- D.2 The async library ecosystem
- D.3 The structured concurrency literature
- D.4 The PEPs that shaped asyncio
- D.5 Things this book did not cover, that you may want next
Glossary
About the Author
From the Author
Preface
What This Book Is About
What This Book Is Not
Who This Book Is For
How to Use This Book
Conventions Used in This Book
Companion Code and Data
How to Contact Us
Acknowledgments
Contents
The Blocking Function
- 1.1 The first version of
news.py - 1.2 What “blocking” actually means
- 1.3 The cost grows with the list
- 1.4 The same shape, one layer down
- 1.5 The question that drives the rest of the book
- Summary
Concurrency, Parallelism, and the GIL
- 2.1 What a thread is, briefly
- 2.2 The threaded version of
news.py - 2.3 Threading the TCP probes
- 2.4 Why the speedup actually works (and the GIL story you have heard wrong)
- 2.5 Why threads are not the answer the book is reaching for
- 2.6 The two scales the book cares about
- 2.7 Where the next chapter goes
- Summary
The Loop, From Scratch
- 3.1 A queue of jobs
- 3.2 A loop that runs the next ready job
- 3.3 Two senses of “ready”, and the generator that gives us pause
- 3.4 The loop sleeps when nothing is ready
- 3.5 Connecting the toy loop to
news.py - Summary
Coroutines, Without the Magic
- 4.1 Generators, the original coroutine
- 4.2 yield as a pause point, one more time
- 4.3 The toy loop
- 4.4 What async def returns
- Polite driver
for x in gen():(nofor; the loop handles it) - 4.5 Coroutines are not running until the loop runs them
- Summary
async and await
- 5.1 What async does to a function definition
- 5.2 What await does at a call site
- 5.3 What can be awaited, and what cannot
- 5.4 The first real asyncio
news.py - 5.5 The cliffhanger
- 5.6 The same keywords, on raw TCP
- Summary
await Yields, Suspends, and Resumes
- 6.1 What await does
- 6.2 What is saved across the pause
- 6.3 How the loop wakes the coroutine back up
- 6.4 A Task is a coroutine that the loop is driving
- 6.5 For the curious: coro.send(value) and the bytecode
- 6.6 For the curious: selectors and the operating system
- 6.7 Watching news.py yield and resume
- Summary
asyncio.run, create_task, gather
- 7.1 asyncio.run as the starting line
- 7.2 What asyncio.run does
- 7.3 await coro is sequential; create_task(coro) is concurrent
- 7.4 asyncio.gather for “run these together”
- 7.5 The fast
news.py - 7.6 Concurrent TCP probes
- 7.7 The lost-task trap
- Summary
The Time Toolbox
- 8.1 asyncio.sleep yields; time.sleep blocks
- 8.2 wait_for for a deadline around an awaitable
- 8.3 asyncio.timeout() as a context manager (Python 3.11+)
- 8.4 asyncio.shield to protect from cancellation
- 8.5
asyncio.waitvsasyncio.gather - 8.6 asyncio.as_completed for results as they arrive
- 8.7 Timeouts and streaming
- Summary
async for, async with, Async Iteration and Resource Management
- 9.1 async for and the iterator protocol
- 9.2 Async generators
- 9.3
async foroveraiohttpresponse bodies - 9.4 async with and the context-manager protocol
- 9.5 Writing your own async context manager
- 9.6 The streaming
news.py - Summary
Tasks, TaskGroups, Cancellation, Exception Groups, and contextvars
- 10.1
Taskis the loop’s unit of work; the coroutine is just the recipe - 10.2 asyncio.TaskGroup: structured concurrency by construction
- 10.3 Cancellation:
task.cancel()andCancelledErrorat the nextawait - 10.4 The bare-except trap
- 10.5 ExceptionGroup and except*
- 10.6 Re-raising CancelledError after cleanup
- 10.7 Canceling a TCP probe
- 10.8 Synchronization primitives
- 10.9 Capping concurrency with
Semaphore: 50 sites, 10 in flight - 10.10 contextvars for task-scoped state
- 10.11 Producer-consumer with
asyncio.Queue - Summary
What Goes Wrong, and How to Find It
- 11.1 The failure mode: one synchronous call freezes the whole loop
- 11.2 The blocking patterns to learn to recognize
- 11.3 The single most useful tool: asyncio’s debug mode
- 11.4 The four warnings the runtime emits
- 11.5 asyncio.all_tasks() and task.get_stack()
- 11.6 aiomonitor: a live REPL inside a running program
- 11.7 Profiling that sees the loop correctly
- 11.8 Logging task names
- 11.9 pdb and async
- 11.10 A guided debugging session on the broken
news.py - 11.11 tcpcheck.py as a diagnostic tool
- Summary
Testing async code
- 12.1 Your first async test
- 12.2 The standard-library alternative
- 12.3 What mocks are, and why async needs a different one
- 12.4 Testing cancellation
- 12.5 Optional: Testing TaskGroup error propagation
- 12.6 Event loop fixture scope
- 12.7 Testing timeouts without real time
- 12.8 The most subtle async test bug
- 12.9 When a test hangs
- 12.10 The full test suite for the fetcher
- Summary
Where asyncio Ends: Executors, Graceful Shutdown
- 13.1 Fixing the parser bug from Chapter 11
- 13.2 The executor toolbox
- 13.3 The decision tree
- Heavy CPU work that needs to scale across cores Use
multiprocessingdirectly. asyncio is the wrong tool for that workload. - 13.4 Bridging from sync to async, and back
- 13.5 Library design: expose async, do not hide it
- 13.6 Graceful shutdown
- 13.7 The final news.py
- Summary
Appendix A: Setting up your environment
- A.1 Python versions and what they buy you
- A.2 The packages the book uses
- A.3 The smallest runnable asyncio script
- A.4 Reading the running example as you go
Appendix B: The asyncio API surface
- Functions
asyncio.wait_for(coro, timeout)Wraps a single awaitable with a deadline; cancels the inner work and raisesTimeoutErroron expiry. 8- Classes and protocols
ExceptionGroupThe exception type that holds multiple exceptions raised by sibling Tasks in a TaskGroup. Caught withexcept*(3.11+). 10- Loop methods worth knowing
loop.set_default_executor(executor)Replaces the loop’s default executor. Useful when the default thread pool is too small for the workload. 13- aiohttp API surface used in the book
response.text()Awaitable that returns the full response body as a decoded string. 5- Where to find the rest
Appendix C: Troubleshooting guide
- C.1 Asyncio runtime warnings, what they mean and what to fix
RuntimeWarning: async generator 'X' was never closedAn async generator was garbage-collected mid-iteration without the consumer running it to completion or callingaclose(). Wrap resources held betweenyieldintry/finally. Callaclose()explicitly when you need cleanup at a specific moment. (Chapter 9, Section 9.2.1.)- C.2 The script runs without errors but is no faster than synchronous
- Slow-callback warnings fire (
Executing took 0.234 seconds) when running withdebug=True. A coroutine ran for too long betweenawait, blocking the loop. Usually a CPU-bound function or a sync I/O call. Move the slow work to a thread pool withasyncio.to_thread. For CPU-bound work that needs cores, use a process pool vialoop.run_in_executor(ProcessPoolExecutor(), ...). (Chapter 11; Chapter 13.) - C.3 The program hangs or never exits
- Ctrl+C does not interrupt the program. No signal handler installed; or a signal handler that schedules async work but
asyncio.runis blocked elsewhere. Install signal handlers withloop.add_signal_handler(sig, callback). Cancel the top-level Task in the callback. The TaskGroup propagates cancellation to children. (Chapter 13.) - C.4 Common errors and what they mean
RuntimeError: Event loop is closed(often during program shutdown) Code is trying to use the loop afterasyncio.runreturned and closed it. Common when a finalizer or__del__method tries to schedule async work. Move any async cleanup into the running coroutine’s normal exit path (finallyblocks,__aexit__). Do not rely on__del__oratexitfor async cleanup.- When the table does not have your symptom
Appendix D: Further reading
- D.1 The asyncio source code
- D.2 The async library ecosystem
- D.3 The structured concurrency literature
- D.4 The PEPs that shaped asyncio
- D.5 Things this book did not cover, that you may want next