October 26, 2023
A comprehensive guide to Futures, Tasks, and Awaitables
In asynchronous programming, managing future values and concurrent operations is crucial. Python provides a robust framework for handling these through Futures, Tasks, and the Awaitable protocol.
A Future is a container for a single value that will be available at some point in the future.
from asyncio import Future
# Creating a future
future = Future()
# Setting a result
future.set_result("Hello from the future!")
# Getting the result
result = future.result()
The foundation of Python's async system is the Awaitable
abstract base class.
from typing import Awaitable
class MyAwaitable:
def __await__(self):
yield
return "Result"
Awaitable
├── Coroutine
├── Future
│ └── Task
└── Generator (with __await__)
import asyncio
async def my_coroutine():
await asyncio.sleep(1)
return "Done!"
# Create a task
task = asyncio.create_task(my_coroutine())
import asyncio
async def set_future_value(future, value, delay):
await asyncio.sleep(delay)
future.set_result(value)
async def main():
# Create a future
future = asyncio.Future()
# Schedule the future to be set
asyncio.create_task(set_future_value(future, "Hello", 1))
# Wait for the result
result = await future
print(result)
asyncio.run(main())
async def process_data(future):
data = await future
return data.upper()
async def main():
future = asyncio.Future()
# Create a task that depends on the future
process_task = asyncio.create_task(process_data(future))
# Set the future's value
future.set_result("hello")
# Wait for the task to complete
result = await process_task
print(result) # Prints: HELLO
async def handle_future_error():
future = asyncio.Future()
try:
future.set_exception(ValueError("Something went wrong"))
result = await future
except ValueError as e:
print(f"Caught error: {e}")
async def task_with_error():
raise ValueError("Task error")
async def main():
task = asyncio.create_task(task_with_error())
try:
await task
except ValueError as e:
print(f"Task failed: {e}")
def callback(future):
print(f"Future completed with result: {future.result()}")
future = asyncio.Future()
future.add_done_callback(callback)
async def main():
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(asyncio.sleep(1))
task2 = tg.create_task(asyncio.sleep(2))
task3 = tg.create_task(asyncio.sleep(3))
Use Tasks for Coroutines
# Prefer this:
task = asyncio.create_task(coroutine())
# Over this:
future = asyncio.Future()
Handle Exceptions
try:
result = await future
except Exception as e:
handle_error(e)
Clean Up Resources
try:
await task
finally:
task.cancel()
Not Awaiting Futures/Tasks
# Wrong:
asyncio.create_task(coroutine())
# Correct:
task = asyncio.create_task(coroutine())
await task
Creating Futures Manually
# Usually unnecessary:
future = asyncio.Future()
# Prefer using tasks:
task = asyncio.create_task(coroutine())
asyncio.gather()
for concurrent executionUnderstanding Futures, Tasks, and Awaitables is crucial for effective asynchronous programming in Python. These components work together to provide a powerful and flexible system for handling concurrent operations.
This comprehensive understanding of Futures and related concepts will help you write more efficient and maintainable asynchronous code in Python.