Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
214: Structured Concurrency with asyncio.TaskGroup
If you've been using asyncio for a while, you've probably leaned heavily on asyncio.gather(). It feels intuitive: you throw a bunch of coroutines into a list, await the result, and you're done. I used to do the same thing until I spent a miserable weekend debugging a production leak where "ghost tasks" were eating up memory and hammering a database long after the original request had timed out.
The Myth that gather() handles task lifecycles
The common misconception is that asyncio.gather() provides a structured way to manage a group of tasks. People assume that if one task in the group fails, the rest are cleaned up. They aren't. gather() is essentially a "fire and hope" mechanism. If one task raises an exception, gather() will immediately propagate that exception to you, but the other tasks keep running in the background. They become orphans.
import asyncio
async def flaky_service():
await asyncio.sleep(1)
print("Service A failed!")
raise RuntimeError("Connection lost")
async def slow_service():
try:
await asyncio.sleep(10)
print("Service B finished")
except asyncio.CancelledError:
print("Service B was cancelled")
async def main():
try:
# This is the dangerous part
await asyncio.gather(flaky_service(), slow_service())
except RuntimeError:
print("Caught the error from Service A")
asyncio.run(main())
# Notice that "Service B was cancelled" is NEVER printed.
# Service B is still running in the background for 10 seconds
# even though the main function has already moved on.
In a real application, this is a nightmare. You end up with "zombie" tasks that hold onto sockets or file handles, and you have no easy way to track them down because the gather() call has already returned.
Structured Concurrency with TaskGroups
This is why Python 3.11 introduced asyncio.TaskGroup. It implements "structured concurrency," a concept borrowed from languages like Go and libraries like Trio. The core idea is simple: if you open a group of tasks, you cannot leave that block until every task in that group has finished—either by completing successfully or by being cancelled.
When you use a TaskGroup, if any task within the group raises an exception, all other remaining tasks in the group are automatically cancelled. No orphans, no zombies.
import asyncio
async def flaky_service():
await asyncio.sleep(1)
print("Service A failed!")
raise RuntimeError("Connection lost")
async def slow_service():
try:
await asyncio.sleep(10)
print("Service B finished")
except asyncio.CancelledError:
print("Service B was cancelled!") # This WILL now trigger
async def main():
try:
async with asyncio.TaskGroup() as tg:
tg.create_task(flaky_service())
tg.create_task(slow_service())
except ExceptionGroup as eg:
print(f"Caught grouped exceptions: {eg}")
asyncio.run(main())
Notice two major changes here. First, we use async with. The block acts as a boundary; the code will not proceed past the end of that indentation until the tasks are resolved. Second, we catch an ExceptionGroup. Because multiple tasks could potentially fail simultaneously, Python doesn't just throw one exception; it wraps all failures into a group.
I strongly recommend moving toward TaskGroup for almost everything. Use gather() only if you specifically need the results returned as a sorted list and you are absolutely certain that your tasks are independent and "leak-proof." For everything else, the safety guardrails of structured concurrency are worth the slightly more verbose syntax.
📋 Practical Task
Build a Fail-Fast Multi-Endpoint Health Monitor
You need to build a health check system that monitors three different microservices. If any critical service fails, the monitor should immediately stop checking the other services to save resources and report the failure.
Requirements:
- Create three coroutines:
check_db(),check_cache(), andcheck_api(). check_db()should simulate a failure by raising aConnectionErrorafter 0.5 seconds.check_cache()andcheck_api()should simulate longer checks (1.5 and 2.0 seconds respectively) usingasyncio.sleep().- Use an
asyncio.TaskGroupto launch all three checks concurrently. - Wrap the
TaskGroupin a try/except block that catchesExceptionGroup. - Ensure that when
check_db()fails, the other two tasks are cancelled immediately (you can verify this by adding atry/except asyncio.CancelledErrorblock inside the cache and api functions to print a "Cancelled" message).
There are no comments for now.