Tasks: The Workhorses of Asyncio
Tasks are how we run multiple coroutines at the same time in Python asyncio. Think of a task as a wrapper around a coroutine that schedules and tracks its execution in the background.
Creating and Managing Tasks
Here’s how to create and use tasks:
import asyncio
import time
async def process_item(item_id, delay):
print(f"Starting to process item {item_id}")
await asyncio.sleep(delay) # Simulates some work
print(f"Finished processing item {item_id} after {delay} seconds")
return f"Result for item {item_id}"
async def main():
# Create two tasks that will run concurrently
task1 = asyncio.create_task(process_item(1, 3))
task2 = asyncio.create_task(process_item(2, 1))
print("Tasks are now running in the background!")
# Wait for both tasks to complete and get their results
result1 = await task1
result2 = await task2
print(f"All done! Results: {result1}, {result2}")
start = time.time()
asyncio.run(main())
print(f"Total time taken: {time.time() - start:.2f} seconds")
Running this code, you’ll see:
- Both tasks start almost simultaneously
- Task 2 completes after 1 second
- Task 1 completes after 3 seconds
- The total execution time is only about 3 seconds, not 4!
This demonstrates the power of concurrency - we’re doing multiple things at once without using threads.
The Magic of Tasks: A Dinner Prep Analogy
Think of tasks like preparing a multi-course dinner:
- Without concurrency (sequential): You completely prepare the salad, then the main course, then the dessert (total time = sum of all prep times)
- With tasks (concurrent): You start the roast in the oven (task1), then prepare the salad (task2) while the roast is cooking. You’re making progress on multiple items at once, and the total time equals only the longest task.
Key Benefits of Tasks
- Automatic scheduling: The event loop handles when tasks run
- State tracking: Tasks keep track of whether they’re running, done, or cancelled
- Result storage: Tasks store their results when done
- Exception handling: Exceptions in tasks can be properly caught and handled
Common Task Operations
# Create a task
task = asyncio.create_task(some_coroutine())
# Check if a task is done
if task.done():
print("Task is completed")
# Cancel a task
task.cancel()
# Get the result (will wait if not done)
result = await task
# Set a timeout
try:
result = await asyncio.wait_for(task, timeout=5.0)
except asyncio.TimeoutError:
print("Task took too long!")
By using tasks effectively, you can build highly concurrent applications that efficiently utilize your system resources without the complexity of thread synchronization.
