The Basics Of Asynchronous Programming In Python

0
2

Asynchronous programming allows code to handle multiple operations concurrently without blocking the execution of the program. It’s particularly useful for I/O-bound tasks (network requests, file operations, database queries) where waiting for external resources would otherwise slow down the program.

Asynchronous programming is used to implement non-blocking operations for I/O bound tasks using the concept of ‘cooperative multitasking’. Unlike pre-emptive multitasking, where CPU is allocated to threads based on the scheduling algorithm, priority, idle time, etc, event loops control execution without explicitly creating user threads in asynchronous programming.

Multitasking (Threads/Processes): This is best for CPU-bound tasks like computation, encryption, compression, AI inference. The OS pre-empts running threads and switches between them using the CPU scheduler.

Asynchronous programming (Async/Await): This is best for I/O-bound tasks where the program spends most of its time waiting. Examples are:

  • Thousands of HTTP requests
  • Waiting for database queries
  • Calling remote APIs
  • Reading/writing slow disks

Async ensures the CPU does not sit idle during these waits.

In synchronous code, operations run one after another. If one task waits for I/O (like network or disk), the entire program pauses. Asynchronous programming allows Python to run multiple tasks concurrently on one thread, using the event loop.

When one task waits, Python immediately switches to another, improving responsiveness and throughput.

Key concepts of asynchronous programming are:

  • Event loop: This is the Python ‘scheduler’ that runs asynchronous tasks. It decides which tasks should run and when to pause/resume them.
  • Coroutine: This refers to cooperative multitasking — a function defined using async def. It waits on the await keyword and explicitly surrenders control to the CPU calling await.
  • Await: This is used to pause the current coroutine in execution. The event loop continues running in the background and can run other non-blocked tasks.

Remember that await can only be called inside a coroutine defined using async def.

A sample program code is given below:

import asyncio

async def myfun(name, delay):

print(f”Task {name} started”)

await asyncio.sleep(delay)

print(f”Task {name} finished”)

async def main():

await asyncio.gather(

myfun(“A”, 2),

myfun(“B”, 1),

myfun(“C”, 3)

)

asyncio.run(main())

Output

Task A started

Task B started

Task C started

Task B finished

Task A finished

Task C finished

Here’s an explanation of the code.

import asyncio

asyncio is Python’s built-in library for writing asynchronous, non-blocking programs. It provides tools like:

  • Event loops
  • Coroutines
  • Tasks
  • Asynchronous I/O helpers
async def myfun(name, delay)

The async keyword makes myfun a coroutine. It can use await inside it and run concurrently with other coroutines.

await asyncio.sleep(delay)

This is the key part. asyncio.sleep() is non-blocking. It tells the event loop ‘pause this coroutine for delay seconds’. While this coroutine waits, the event loop can run other tasks. No CPU is wasted, and the program does not freeze.

This means:

  • Task A waits 2 seconds
  • Task B waits 1 second
  • Task C waits 3 seconds

But all three tasks wait concurrently. asyncio.gather schedules all three coroutines to run at the same time.

The timeline becomes:

  • Time 0s → Start A, B, C
  • Time 1s → B finishes
  • Time 2s → A finishes
  • Time 3s → C finishes

So the total runtime is 3 seconds and not 2 + 1 + 3 = 6 seconds.

asyncio.run(main())

This creates an event loop, runs the main() coroutine, waits until all tasks inside it complete, and closes the event loop.

This is how the event loop works:

  • Picks a coroutine ready to run.
  • Runs it until it explicitly calls await.
  • Pauses it and stores its state.
  • Switches to another ready coroutine.
  • Gets back to a blocked coroutine when it becomes ready again.

The important question is: what does the event loop do when a previously blocked coroutine is ready, but another coroutine is already running? Well, the event loop never interrupts or pre-empts a running coroutine. Python async equals cooperative multitasking, and not pre-emptive multitasking. So, the event loop will let the currently running coroutine continue until it reaches an await (or returns). Only then will it switch and resume the coroutine that just became ready.

A coroutine must voluntarily yield the CPU by calling one of the following:

  • Await <someio>
  • Await ayncio.sleep
  • Return

Then only event loop can switch — there is no pre-emption. So, if a coroutine never awaits, the event loop never switches to another task. Therefore, you must never write blocking code inside a coroutine.

Happy asynchronous programming in Python!

LEAVE A REPLY

Please enter your comment!
Please enter your name here