Skip to main content

7.8 - Coroutines

7.8.1 - Introduction to Coroutines

Coroutines are a powerful feature in Python that allows for efficient asynchronous programming. They enable functions to be paused and resumed, making them ideal for tasks that involve waiting for I/O operations. This guide will provide an understanding of coroutines, their syntax, and practical use cases.


7.8.2 - Understanding async and await

7.8.2.1 - The async Keyword

  • Definition: The async keyword is used to define a coroutine. It modifies a function, indicating that the function is an asynchronous coroutine rather than a normal function.
  • Usage: Placed before def, as in async def function_name():. This creates a coroutine function.
  • Behavior: When called, an async function doesn't execute immediately. Instead, it returns a coroutine object that can be executed asynchronously.

7.8.2.2 - The await Keyword

  • Definition: await is used to pause the coroutine's execution until the awaited task is complete. It can only be used inside an async function.
  • Usage: Applied before calling a coroutine or any awaitable object, like await coroutine_name().
  • Behavior: Pauses the execution of the coroutine where it is used and waits for the awaited task to complete. During this pause, other tasks can run.

7.8.2.3 - Interaction and Workflow

async and await work together to manage asynchronous tasks. When await is called in a coroutine, it tells the Python event loop to run other tasks until the awaited task completes, at which point the coroutine resumes.


7.8.3 - Creating and Running Coroutines

To create a coroutine, use the async def statement. To run a coroutine, you must await it within another coroutine or use an event loop, like asyncio.run().

import asyncio

async def my_coroutine():
print("My Coroutine")

# Running the coroutine with an event loop
asyncio.run(my_coroutine())

7.8.4 - Coroutines Case Studies

7.8.4.1 - Asynchronous Web Request

Handling web requests asynchronously is a common use case for coroutines. This example uses aiohttp to make non-blocking HTTP requests.

import aiohttp
import asyncio

async def fetch_url(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()

async def main():
html = await fetch_url("http://example.com")
print(html)

asyncio.run(main())

7.8.4.2 - Asynchronous File Operations

Performing file operations asynchronously can improve the efficiency of I/O-bound tasks.

async def read_file_async(file_path):
await asyncio.sleep(1) # Simulating an I/O-bound task
with open(file_path, 'r') as file:
return file.read()

async def main():
content = await read_file_async('example.txt')
print(content)

asyncio.run(main())

7.8.4.3 - Producer-Consumer Using Asyncio

This demonstrates the producer-consumer pattern in asynchronous programming.

async def producer(queue):
for i in range(10):
await queue.put(i)
print(f'Produced {i}')
await asyncio.sleep(1)

async def consumer(queue):
while True:
item = await queue.get()
print(f'Consumed {item}')
queue.task_done()

async def main():
queue = asyncio.Queue()
producer_coroutine = producer(queue)
consumer_coroutine = consumer(queue)
await asyncio.gather(producer_coroutine, consumer_coroutine)

asyncio.run(main())

7.8.4.4 - Asynchronous Timer

Creating a simple asynchronous timer to execute tasks at regular intervals.

async def timer(interval, task):
while True:
await asyncio.sleep(interval)
await task()

async def print_time():
print(f"Task executed at {time.strftime('%X')}")

async def main():
await timer(5, print_time) # Execute `print_time` every 5 seconds

asyncio.run(main())

7.8.4.5 - Chat Server with Asyncio

Building an asynchronous chat server to handle multiple client connections.

import asyncio

async def handle_client(reader, writer):
while True:
data = await reader.read(100)
if data:
message = data.decode()
print(f"Received: {message}")
writer.write(data)
await writer.drain()
else:
print("Closing connection")
writer.close()
break

async def main():
server = await asyncio.start_server(
handle_client, '127.0.0.1', 8888)
async with server:
await server.serve_forever()

asyncio.run(main())