How to handle blocking tasks in Asynchronous functions efficiently


When using asyncio.create_task(function1) and asyncio.create_task(function2), you are creating two asynchronous tasks that will run concurrently within the same event loop. Whether or not these tasks run efficiently depends on the nature of the tasks themselves.

Understanding Asynchronous Tasks

If function1 and function2 are asynchronous functions (i.e., they use async def and contain await statements), they will run efficiently as long as they perform non-blocking operations, such as:

  • Network I/O
  • Disk I/O
  • Timers (e.g., await asyncio.sleep())
  • Other asynchronous operations

In this situation, asyncio will handle the scheduling, suspending tasks when they are waiting for I/O operations to complete, and resuming them when the operations are ready. This results in efficient concurrency.

Inefficiency with Blocking Code

If function1 and function2 contain blocking operations (e.g., CPU-bound tasks, time.sleep(), or blocking I/O), they will not run efficiently. Blocking code will block the entire event loop, preventing other tasks from running concurrently.

Example of Efficient Async Tasks

Here’s an example of efficiently running non-blocking asynchronous tasks using asyncio.create_task():

import asyncio

async def function1():
print('Function 1: Start')
await asyncio.sleep(2) # Simulate non-blocking I/O
print('Function 1: End')

async def function2():
print('Function 2: Start')
await asyncio.sleep(3) # Simulate non-blocking I/O
print('Function 2: End')

async def main():
# Create tasks to run them concurrently
task1 = asyncio.create_task(function1())
task2 = asyncio.create_task(function2())

# Optionally await the tasks if you want to wait for their completion
await task1
await task2

# Run the main async function
asyncio.run(main())

Handling Blocking Code with Executors

If you need to run blocking I/O tasks, you should offload them to a thread or process pool to avoid blocking the event loop:

import asyncio
from concurrent.futures import ThreadPoolExecutor
import time

def blocking_io_task1():
print('Blocking Task 1: Start')
time.sleep(2) # Simulate blocking I/O
print('Blocking Task 1: End')

def blocking_io_task2():
print('Blocking Task 2: Start')
time.sleep(3) # Simulate blocking I/O
print('Blocking Task 2: End')

async def main():
loop = asyncio.get_running_loop()

with ThreadPoolExecutor() as pool:
# Offload blocking tasks to the thread pool
task1 = loop.run_in_executor(pool, blocking_io_task1)
task2 = loop.run_in_executor(pool, blocking_io_task2)

# If you need to await their completion, you can do so
await task1
await task2

# Run the main async function
asyncio.run(main())

Summary

  • Efficient Async Tasks: Use async def functions with await statements for non-blocking operations.
  • Handling Blocking Code: Offload blocking tasks to a thread or process pool using loop.run_in_executor.

By carefully distinguishing between blocking and non-blocking tasks, you can ensure that your tasks run efficiently when using asyncio.create_task().


Author: robot learner
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source robot learner !
  TOC