Keywords: asyncio | gather | wait | TaskGroup | Python | asynchronous programming
Abstract: This article provides an in-depth comparison of three key functions in Python's asyncio library: asyncio.gather, asyncio.wait, and asyncio.TaskGroup. Through code examples and detailed analysis, it explains their differences in task execution, result collection, exception handling, and cancellation mechanisms, helping developers choose the right tool for specific scenarios.
Introduction
Asynchronous programming plays a crucial role in modern Python applications, with the asyncio library offering powerful tools for managing concurrent tasks. Among these, asyncio.gather, asyncio.wait, and asyncio.TaskGroup are commonly used functions for executing multiple coroutines simultaneously. Although they share overlapping functionalities, each has distinct details. This article reorganizes logic based on Q&A data and official documentation to provide a comprehensive comparison.
Functionality and Usage of asyncio.gather
asyncio.gather is a high-level function primarily used to collect results from multiple awaitables. It returns a Future object that resolves to a list of results in the order of input. This function supports grouping tasks and overall cancellation, for example, by calling group.cancel() to cancel a set of tasks. Additionally, the return_exceptions=True parameter allows exceptions to be returned as results instead of being immediately raised.
Here is a rewritten code example demonstrating the basic usage of asyncio.gather:
import asyncio
import random
async def sample_coro(tag):
print(">", tag)
await asyncio.sleep(random.uniform(1, 3))
print("<", tag)
return tag
async def main():
tasks = [sample_coro(f"task_{i}") for i in range(3)]
results = await asyncio.gather(*tasks)
print(results)
asyncio.run(main())In this example, all tasks execute concurrently, and results are collected in order. If a task raises an exception, it propagates immediately by default, but using return_exceptions=True can prevent interruption of other tasks.
Functionality and Usage of asyncio.wait
asyncio.wait is a lower-level function that waits for a set of awaitables and returns completed and pending tasks based on specified conditions. Through the return_when parameter, it controls waiting behavior, such as asyncio.FIRST_COMPLETED (return when the first task completes) or asyncio.ALL_COMPLETED (return when all tasks complete). It also supports timeout settings but does not automatically cancel tasks.
Here is a rewritten code example showing the usage of asyncio.wait:
import asyncio
import random
async def sample_coro(tag):
print(">", tag)
await asyncio.sleep(random.uniform(0.5, 2))
print("<", tag)
return tag
async def main():
tasks = [asyncio.create_task(sample_coro(i)) for i in range(5)]
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
for task in done:
print(f"Completed: {task.result()}")
print(f"Pending tasks: {len(pending)}")
asyncio.run(main())Unlike asyncio.gather, asyncio.wait does not directly return a list of results but rather sets of tasks. Exception handling is more flexible but requires manual iteration over tasks to catch exceptions.
Functionality and Usage of asyncio.TaskGroup
asyncio.TaskGroup is a new feature introduced in Python 3.11 that automatically manages a group of tasks via an asynchronous context manager. When the context exits, all tasks are automatically awaited. If any task fails (other than CancelledError), remaining tasks are cancelled, and an exception group is raised, providing stronger guarantees for structured concurrency.
Here is a rewritten code example demonstrating the basic usage of asyncio.TaskGroup:
# Python 3.11+ only
import asyncio
async def sample_coro(name):
print(f"Start {name}")
await asyncio.sleep(1)
print(f"End {name}")
return name
async def main():
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(sample_coro("task1"))
task2 = tg.create_task(sample_coro("task2"))
print("All tasks completed")
asyncio.run(main())TaskGroup simplifies task management without the need for explicit calls to gather or wait. It is particularly suitable for scenarios requiring robust exception handling and cancellation semantics.
Comparison and Use Cases
asyncio.gather is ideal for simple result collection and overall cancellation, but exception handling may interrupt other tasks. asyncio.wait offers finer control, such as waiting for the first task to complete or setting timeouts, but requires manual result and exception handling. asyncio.TaskGroup combines ease of use with safety, automatically handling task completion and exception propagation, and is recommended for Python 3.11+.
In terms of exception handling, asyncio.gather stops at the first exception by default, while asyncio.wait continues executing other tasks. TaskGroup automatically cancels remaining tasks on failure and raises a combined exception. Developers should choose based on application needs: use gather for result order and simplicity, wait for low-level control, and TaskGroup for modern safety and conciseness.
Conclusion
asyncio.gather, asyncio.wait, and asyncio.TaskGroup each have their strengths and are not merely overlapping in functionality but designed for different scenarios. By understanding their core differences, developers can build asynchronous applications more efficiently. It is recommended to prioritize TaskGroup in Python 3.11+ for better error handling and concurrency safety.