Asynchronous programming has become increasingly popular in Python. The asyncio module provides infrastructure for writing asynchronous code using coroutines and tasks. But what exactly is the difference between coroutines and tasks? This article will explain the key distinctions.
Coroutines
A coroutine is a specialized Python function that can suspend and resume execution. Normal Python functions execute straight through and return when complete. But coroutines can pause in the middle of execution and let other coroutines run, then later pick up where they left off.
Here is a simple coroutine example:
import asyncio
async def my_coroutine():
print('My coroutine is running')
await asyncio.sleep(1)
print('My coroutine is done')
The
So coroutines provide the components for asynchronous, non-blocking code. But on their own they don't run. For that we need tasks.
Tasks
While coroutines define asynchronous behavior, tasks are what actually run the coroutines. We can think of a task as a wrapper around a coroutine which manages the execution.
Creating a task from a coroutine is simple:
import asyncio
async def my_coroutine():
# coroutine code
my_task = asyncio.create_task(my_coroutine())
The event loop can then run Python code concurrently by switching between tasks:
import asyncio
async def coroutine_1():
print('Coroutine 1 starts')
await asyncio.sleep(1)
print('Coroutine 1 ends')
async def coroutine_2():
print('Coroutine 2 starts')
await asyncio.sleep(2)
print('Coroutine 2 ends')
async def main():
task1 = asyncio.create_task(coroutine_1())
task2 = asyncio.create_task(coroutine_2())
await task1
await task2
asyncio.run(main())
This concurrently runs both coroutines by creating tasks from them. The key difference from coroutines is that tasks actually execute the coroutine code.
Coroutines Define, Tasks Run
A summary of the distinction:
So coroutines provide the components, but tasks are what make them run.
Here is one way to think about it:
The recipe defines the steps, but someone still needs to do the cooking!
Practical Example
Here is a practical example of using both coroutines and tasks in an application.
Let's say we need to make multiple API calls concurrently. We define an
import asyncio
import httpx
async def api_call(url):
print(f'Making API call to {url}')
async with httpx.AsyncClient() as client:
resp = await client.get(url)
print(f'API call completed, status {resp.status_code}')
return resp.json()
Then we create tasks from
import asyncio
async def main():
url1 = 'https://api.domain1.com/endpoint1'
url2 = 'https://api.domain2.com/endpoint2'
task1 = asyncio.create_task(api_call(url1))
task2 = asyncio.create_task(api_call(url2))
responses = await asyncio.gather(task1, task2)
print(responses[0], responses[1])
asyncio.run(main())
So the
Key Takeaways
So in summary, coroutines provide asynchronous components while tasks are what actually run the coroutines and enable concurrency in Python. Together they allow us to make asynchronous, non-blocking applications.