top of page

How Does Python Asyncio Handle Concurrent I/O Bound Applications?

Writer: k4666945
k4666945
4 days ago
3 min read
python course

Introduction

The reason why most slow programs written in Python are slow is not due to complex mathematical operations but because of waiting. A network request or reading from a file can cause your program to wait for whole seconds. Asyncio does it by allowing one thread to switch to another task while some slow tasks are waiting. This article will help you understand how this switch happens in a clear and easy way with several examples. In addition, it will explain the concepts that most Advanced Python Training require you to know before getting into production code. Once you get the model right, writing efficient network code will become much easier than it initially seems. So, let’s start with the actual problem that this library was designed to solve.


Why waiting is the real problem.

IO-bound process uses most of its time waiting for the data to be received either from a network, disk, or other sources. At the same time, the CPU is idle, but can be accessed by no other script at the moment.

Now consider fetching fifty web pages one by one, with each request taking half a second. So that simple script will take about twenty-five seconds to complete with all the idle time. However, asyncio can help only when requests are scheduled as concurrent tasks and with real non-blocking calls. In that case, all fifty requests wait at once, and thus the total time taken is around the slowest response time.


The event loop at the centre

The event loop is an essential part of asyncio and runs in a single-threaded manner. The event loop contains the list of tasks and keeps on executing each task until the task becomes idle.

Then, the task releases control, and the event loop picks up another task from the queue. The operating system alerts the event loop in case there is any data at the socket. Thus, it is not required to perform any wasteful polling. This is cooperative multitasking where tasks change states depending on their will.


The building blocks

There are four elements that feature in virtually any asyncio code, and each has a very precise purpose.

Piece

What it does

Coroutine

A function defined with async def that can pause itself

await

Marks the spot where a coroutine pauses and lets others run

Task

Wraps a coroutine so the loop can schedule it

Gather or Task Group

Simultaneously executes multiple tasks

Also, a simple coroutine will not do anything until it is scheduled by the loop, something that puzzles newbies a lot.


A small practical example

Here is a tiny script where three simulated slow calls each take exactly two seconds.

Here, the gather function packages each coroutine into a task, so all three begin waiting simultaneously. When done sequentially, this will take six seconds, but gather accomplishes it within two seconds. If you practice it in Python Online Classes, vary the delays and notice how the total time depends on the maximum time. Then substitute asyncio. sleep with a true non-blocking call, and the same pattern applies to live traffic.


Asyncio, threads, and processes compared

I get this question quite often: when should I choose asyncio over threads or over worker processes? A nice exercise in Advanced Python Training is to compare two tasks: scraping multiple web pages and resizing multiple images.

Feature

Asyncio

Threads

Processes

Best for

Many waiting tasks

Waiting tasks that use blocking libraries

Heavy CPU work

Memory cost

Very low

Moderate

High

Switching

At await points

Decided by the system

Separate programs

Shared data risk

Lower

Higher

Needs special sharing

So the short answer is simple: choose asyncio for waiting work, and choose processes for heavy calculating.


Mistakes that break concurrency

Concurrency is easy to mess up through some mistakes, but Asyncio does not let those mistakes spoil everything. Try making an error with each bug listed here to ruin your small program during Python Classes in Gurgaon.

  • A blocking call like time.sleep() will block the entire loop, and all other coroutines will wait until this blocking call ends.

  • Without using await in a function call, you will receive a coroutine object, but Python will only notify you that it was never awaited.

  • Large computations in a coroutine block the loop and have to be done separately.

  • Starting an infinite number of coroutines right from the start will cause an overload of the server, so a semaphore should be used.


Conclusion

This is due to the fact that tasks that are put on hold give control back to the loop, which swiftly gets busy performing other pending tasks. Asyncio performs wonders when an application needs to wait for the network, but does not speed up calculations. Begin with just one small program, time the savings, then move on to bigger programs. Keep building, timing, and improving in this manner and the ideas will quickly start to come naturally.

Comments


bottom of page