python Logoasyncio

asyncio is Python's built-in library for writing concurrent code using the `async`/`await` syntax. It is a framework for writing single-threaded concurrent code using coroutines, multiplexing I/O access over sockets and other resources, running network clients and servers, and other related primitives.

Core Concept: Cooperative Multitasking
Unlike traditional threading where the operating system preemptively switches between threads, asyncio uses cooperative multitasking. This means that a function (a 'coroutine') explicitly yields control back to the 'event loop' when it encounters an operation that would otherwise block (e.g., waiting for network data, reading from a file). While one coroutine is waiting, the event loop can run other ready coroutines, making efficient use of a single thread.

Key Components:
- Coroutines (`async def`): Functions defined with `async def`. They are special functions that can be paused and resumed. When an `async` function is called, it doesn't execute immediately; instead, it returns a coroutine object that can be `await`ed.
- `await`: Used inside an `async` function to pause its execution until the 'awaitable' (another coroutine, a Task, or a Future) completes. While paused, the event loop can execute other tasks.
- Event Loop: The heart of asyncio. It manages and distributes events, runs tasks, and handles I/O operations. It keeps track of which coroutines are ready to run and which are waiting for something.
- Tasks (`asyncio.create_task`): Wraps a coroutine and schedules it to be run by the event loop. Tasks are a subclass of `Future` and represent an independent unit of execution.
- `asyncio.run()`: The high-level entry point to run the event loop and execute the main coroutine. It handles the setup and shutdown of the event loop.

Advantages:
- High Concurrency with Low Overhead: Achieves concurrency without the overhead of multiple OS threads/processes, making it efficient for I/O-bound workloads.
- Non-Blocking I/O: Ideal for applications that spend most of their time waiting for external resources (network requests, database queries, file I/O).
- Improved Responsiveness: Keeps the application responsive by not blocking the entire program during slow operations.
- Scalability: Can handle many concurrent connections or operations efficiently on a single thread.

Use Cases:
- Building high-performance web servers and API clients.
- Developing network applications (proxies, chat servers).
- Asynchronous database access.
- Data streaming and processing.
- Any I/O-bound application where responsiveness and scalability are critical.

Example Code

import asyncio
import time

async def say_hello(name, delay):
    print(f"[{time.strftime('%H:%M:%S')}] {name}: Starting...")
    await asyncio.sleep(delay)  Simulate an I/O-bound operation
    print(f"[{time.strftime('%H:%M:%S')}] {name}: Finished after {delay} seconds.")
    return f"Hello, {name}!"

async def main():
    print(f"[{time.strftime('%H:%M:%S')}] Main: Starting main coroutine.")

     Create tasks for concurrent execution
    task1 = asyncio.create_task(say_hello("Alice", 2))
    task2 = asyncio.create_task(say_hello("Bob", 1))
    task3 = asyncio.create_task(say_hello("Charlie", 3))

     Await the completion of all tasks
     asyncio.gather runs awaitables concurrently and waits for them to complete.
    results = await asyncio.gather(task1, task2, task3)

    print(f"[{time.strftime('%H:%M:%S')}] Main: All tasks completed.")
    for res in results:
        print(f"[{time.strftime('%H:%M:%S')}] Result: {res}")

if __name__ == "__main__":
     asyncio.run() is the high-level entry point for running asyncio programs.
     It handles creating a new event loop, running the main coroutine, and closing the loop.
    asyncio.run(main())
    print(f"[{time.strftime('%H:%M:%S')}] Program finished.")