Burak's Dev Blog

Asyncio in Python: A Practical Guide

Burak's Dev Blog

Topics Covered:

Introduction

In Python's vast ocean of libraries, there's one that often sails under the radar, yet has the power to revolutionize the way we code: asyncio. It's a library designed for writing single-threaded concurrent code, a way to juggle multiple tasks, improving efficiency and performance.

The Cornerstone of Asyncio: Coroutines

Asyncio is built around coroutines. These are special functions that you can pause ('await') and resume at any time. This is what allows asyncio to handle multiple tasks concurrently. In Python, we declare coroutines using the async def syntax.

import asyncio

async def hello():
    print('Hello')
    await asyncio.sleep(1)
    print('World')

# Running the coroutine
asyncio.run(hello())

In the above code, asyncio.sleep(1) is akin to a placeholder for a time-consuming task. The await keyword before it allows the execution to pause, giving other tasks a chance to run.

Await: The Control Switch

The await keyword is the control switch in the machinery of asyncio. It's what tells a coroutine to pause and resume.

import asyncio

async def main():
    print('Hello')
    await asyncio.sleep(1)
    print('World')

asyncio.run(main())

In this snippet, when Python encounters await asyncio.sleep(1), it effectively says, "Okay, I'll pause here, see if there are other tasks to run, and come back after one second to continue."

Managing Multiple Tasks

One of asyncio's standout features is its ability to manage multiple tasks concurrently. This is especially useful when you have several tasks that need to run at the same time.

import asyncio
import time

async def heavy_task(id, sec):
    print(f'Starting task {id}')
    await asyncio.sleep(sec)  # Simulating heavy task with sleep
    print(f'Finished task {id}')

async def main():
    task1 = asyncio.create_task(heavy_task(1, 3))
    task2 = asyncio.create_task(heavy_task(2, 2))

    await task1
    await task2

start = time.time()
asyncio.run(main())
print(f'Total elapsed time: {time.time() - start}')

In this example, asyncio.sleep mimics a heavy task. We create two tasks, which represent two concurrent operations. The total elapsed time will be roughly equal to the longest task, demonstrating the concurrent execution of tasks.

Asyncio.gather: The Task Orchestrator

For an even higher-level approach to running tasks concurrently, asyncio provides asyncio.gather(). It runs multiple awaitable objects (like coroutines) and returns a single awaitable object that gives a list of results when awaited.

import asyncio

async def count(id, n):
    for i in range(1, n+1):
        print(f'Task {id}: {i}')
        await asyncio.sleep(1)
    return f'Task {id} finished counting to {n}'

async def main():
    task1 = count(1, 3)
    task2 = count(2, 2)
    task3 = count(3, 4)
    
    results = await asyncio.gather(task1, task2, task3)
    for result in results:
        print(result)

asyncio.run(main())

In this piece of code, we use asyncio.gather() to run three tasks concurrently. When all tasks are complete, asyncio.gather() provides a list of their results.

Conclusion

Asyncio is a powerful tool that allows us to write asynchronous code in Python. It may seem like uncharted territory at first, but with a grasp of the basics and some practice, you'll soon be charting your course in the sea of asynchronous programming. Happy coding!

#python #asynchronous #asyncio