Coroutines
Normal functions are like single-focus tasks.
Coroutines allow for multitasking by alternating between different activities.
async def coroutine_multiply_be_two(number: int) -> int:
return nubmer * 2
def multiply_by_two(number: int) -> int:
return number * 2
multiply_by_two runs immediately and returns the result.
coroutine_multiply_by_two, the code inside the coroutine doesn’t execute right away. Instead, it returns a coroutine object, which means the coroutine has been defined but hasn’t been run yet.
Essential Understanding of Coroutines
They don’t execute upon being called. Instead, they produce a coroutine object that can be scheduled and executed later by an event loop.
Event Loop
The event loop ensures that your program remains responsive by handling these operations without blocking the main execution flow.
By coordinating the execution of coroutines, the event loop facilitates concurrent task processing.
Tools
To execute coroutines, we use specific tools provided by Python’s asyncio library. The asyncio.run() function starts the event loop and run main coroutine, while keyword await allows us to pause and resume coroutines.
Running
import asyncio
async def fetch_data(data_id: int) -> str:
print(f'Fetching data for ID {data_id}')
await asyncio.sleep(2) # Simulates a delay like a network request
print(f'Data fetched for ID {data_id}')
return f'Data {data_id}'
async def compute_result(value: int) -> int:
await asyncio.sleep(1) # Simulates a delay like a computation
return value * 2
async def process_data() -> None:
data = await fetch_data(1) # 2s
result = await compute_result(5) # 1s
print(f'Result: {result}')
print(f'Processed Data: {data}')
asyncio.run(process_data())
asyncio.run(process_data()) is the starting point.
await keyword is used, the coroutine pauses, allowing other operations to run.
Utilizing
Inefficient
import asyncio
async def fetch_data(data_id: int) -> None:
print(f'Fetching data for ID {data_id}')
await asyncio.sleep(3) # Simulates waiting for a response from a server
print(f'Finished fetching data for ID {data_id}')
async def main() -> None:
await fetch_data(1)
await fetch_data(2)
await fetch_data(3)
asyncio.run(main())
Each fetch_data waits for the previous one to complete.
This sequential execution is straightforward but can be inefficient for tasks that can be performed concurrently.
Efficient
import asyncio
async def fetch_data(data_id: int) -> None:
print(f'Fetching data for ID {data_id}')
await asyncio.sleep(3) # Simulates waiting for a response from a server
print(f'Finished fetching data for ID {data_id}')
async def main() -> None:
# Create tasks for concurrent execution
task1 = asyncio.create_task(fetch_data(1))
task2 = asyncio.create_task(fetch_data(2))
task3 = asyncio.create_task(fetch_data(3))
# Await all tasks
await task1
await task2
await task3
asyncio.run(main())
Each fetch_data starts running as soon as it is created as a task. All run concurrently.
Reference: https://medium.com/python-features/understanding-coroutines-tasks-in-depth-in-python-af2a4c0e1073