Python Asyncio: A Guide to Asynchronous Programming and Concurrency
Tags: programming, python, productivity, webdev
Published: 2023-11-15 08:37:18

## Table of Contents

1. [What is Concurrency?](#what-is-concurrency)
2. [Writing Concurrent Code in Python](#writing-concurrent-code-in-python)
3. [Libraries for Asynchronous Programming](#libraries-for-asynchronous-programming)
4. [Getting Started with asyncio](#getting-started-with-asyncio)
   - [Asynchronous Programming in Action](#asynchronous-programming-in-action)
     - [Synchronous Code](#synchronous-code)
     - [Asynchronous Code](#asynchronous-code)
     - [Walkthrough of Asynchronous Code](#walkthrough-of-asynchronous-code)
5. [Most Popular asyncio Functions](#most-popular-asyncio-functions)
6. [Other Relevant Information](#other-relevant-information)
   - [Debug Mode](#debug-mode)
7. [Conclusion](#conclusion)

Python, known for its synchronous code execution, processes tasks line by line, causing delays when tasks take time to run. In a previous [article](https://dev.to/greyisheepai/keywords-in-asynchronous-programming-concurrency-multiprocessing-all-explained-44b3), we discussed parallelization, the Global Interpreter Lock (GIL), and its impact on parallel execution.


![Good news everyone](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5vamf88450yjnvwkrqui.jpeg)

To overcome this, we turn to concurrency.

## What is Concurrency?

Concurrency involves running multiple tasks simultaneously by efficiently managing their wait times.

### Writing Concurrent Code in Python

To achieve concurrency in Python, we turn to asynchronous programming, briefly discussed [here](https://dev.to/greyisheepai/keywords-in-asynchronous-programming-concurrency-multiprocessing-all-explained-44b3).

### Libraries for Asynchronous Programming

Three notable libraries for asynchronous programming are:
1. asyncio
2. anyio
3. aiohttp

This article focuses on asyncio, the foundation for anyio and aiohttp.

## Getting Started with asyncio

asyncio enables writing concurrent code using the async/await syntax. It serves as the base for various Python asynchronous frameworks, offering high-performance solutions for network and web servers, database connections, distributed task queues, and more.

asyncio is particularly suitable for IO-bound and high-level structured network code. Its high-level APIs allow:
- Running Python coroutines concurrently
- Performing network IO and IPC
- Controlling subprocesses
- Distributing tasks via queues
- Synchronizing concurrent code

Low-level APIs are also available for creating and managing event loops, implementing efficient protocols, and bridging callback-based libraries with async/await syntax.

## Asynchronous Programming in Action

### Synchronous Code
```python
#sync.py
import time

def print_message(message, delay):
    time.sleep(delay)
    print(message)

def main():
    print_message("Hello", 2)
    print_message("Sync", 1)
    print_message("World", 3)

if __name__ == "__main__":
    start_time = time.time()
    main()
    end_time = time.time()
    print(f"Total execution time: {end_time - start_time} seconds")
```

![sync code takes about double the time as async code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r9fkn33e4yuv24hb5q3g.jpg)

### Asynchronous Code
```python
#async.py
import asyncio
import time

# Define a simple asynchronous function
async def print_message(message, delay):
    # Simulate some asynchronous operation, like I/O or network request
    await asyncio.sleep(delay)
    print(message)

# Define the main asynchronous function
async def main():
    # Use asyncio.gather to run multiple asynchronous functions concurrently
    # Wait for each task to complete in order
    await asyncio.gather(
        print_message("Async", 2),
        print_message("Hello", 1),
        print_message("World", 3)
    )

# Run the event loop to execute the asynchronous code
if __name__ == "__main__":
    # Record the start time for measuring execution time
    start_time = time.time()

    # Run the main asynchronous function using asyncio.run()
    asyncio.run(main())

    # Calculate and print the total execution time
    end_time = time.time()
    print(f"Total execution time: {end_time - start_time} seconds")
```

![async code takes about half the time to execution](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4nadlrb5shntaznrcnv2.jpg)

### Walkthrough of Asynchronous Code:

1. Import the required libraries: `asyncio` and `time`.
2. Define an asynchronous function `print_message(message, delay)`. This function simulates asynchronous operations using `asyncio.sleep(delay)` and prints the given message.
3. Define the main asynchronous function `main()`. It uses `asyncio.gather` to concurrently run multiple instances of the `print_message` function with different messages and delays.
4. Check if the script is being run directly using `if __name__ == "__main__":`.
5. Record the start time using `start_time = time.time()`.
6. Run the main asynchronous function using `asyncio.run(main())`.
7. Record the end time using `end_time = time.time()`.
8. Print the total execution time: `print(f"Total execution time: {end_time - start_time} seconds")`.

## Most Popular asyncio Functions

1. `run()`: Create an event loop, run a coroutine, and close the loop.
2. `Runner`: A context manager simplifying multiple async function calls.
3. `Task`: Task object.
4. `TaskGroup`: A context manager holding a group of tasks, providing a way to wait for all to finish.
5. `create_task()`: Start an asyncio Task and return it.
6. `current_task()`: Return the current Task.
7. `all_tasks()`: Return all unfinished tasks for an event loop.
8. `await sleep()`: Sleep for a number of seconds.
9. `await gather()`: Schedule and wait for things concurrently.
10. `await wait_for()`: Run with a timeout.
11. `await shield()`: Shield from cancellation.
12. `await wait()`: Monitor for completion.
13. `timeout()`: Run with a timeout.
14. `to_thread()`: Asynchronously run a function in a separate OS thread.
15. `run_coroutine_threadsafe()`: Schedule a coroutine from another OS thread.
16. `for in as_completed()`: Monitor for completion with a for loop.

## Other Relevant Information

- **Debug Mode:**
  - Enable by setting `PYTHONASYNCIODEBUG=1`, using Python Development Mode, passing `debug=True` to `asyncio.run()`, or calling `loop.set_debug()`.
  - Configure the logger level to `logging.DEBUG`.
  - Display ResourceWarning warnings using `-W default` command line option.

With debug mode:
- asyncio checks for unawaited coroutines.
- Non-threadsafe asyncio APIs raise exceptions if called from the wrong thread.
- I/O selector execution time is logged if it takes too long.
- Callbacks longer than 100 milliseconds are logged.

## Conclusion
Incorporate asyncio into your Python projects for efficient concurrent and asynchronous programming. Explore its rich set of functions and keep in mind the debugging options for smoother development.

I will be writing an article on anyio. The asynchronous programming library used by fastapi and starlette to handle requests and other tasks.

In the mean time, follow me, jump to comments, and leabe positive reactions. 

Happy asynchronous coding!!!