Running Blocking Code with run_in_executor()
Svep för att visa menyn
Not every library supports asyncio. requests, pandas, file system calls, and CPU-heavy functions are all blocking – calling them directly inside a coroutine freezes the entire event loop. run_in_executor() moves blocking code to a thread pool, freeing the event loop to keep running.
The Problem
Calling a blocking function inside an async context stalls all other coroutines until it returns:
123456789101112131415161718192021import asyncio import time import nest_asyncio nest_asyncio.apply() # Blocking call inside a coroutine – freezes the event loop async def process_data(label): print(f"Starting {label}") time.sleep(1) # Blocking – no other coroutine runs during this print(f"Done {label}") async def main(): start_time = time.time() await asyncio.gather( process_data("task_A"), process_data("task_B"), ) print(f"Total time: {time.time() - start_time:.2f}s") # ~2s – sequential asyncio.run(main())
Using run_in_executor()
loop.run_in_executor(executor, func, *args) runs func in a thread pool and returns an awaitable. Pass None as the executor to use the default ThreadPoolExecutor:
123456789101112131415161718192021222324import asyncio import time import nest_asyncio nest_asyncio.apply() # Moving a blocking call to a thread pool def blocking_process(label): print(f"Starting {label}") time.sleep(1) # Runs in a thread – event loop stays free print(f"Done {label}") async def main(): loop = asyncio.get_event_loop() start_time = time.time() await asyncio.gather( loop.run_in_executor(None, blocking_process, "task_A"), loop.run_in_executor(None, blocking_process, "task_B"), ) print(f"Total time: {time.time() - start_time:.2f}s") # ~1s – concurrent asyncio.run(main())
A Realistic Example: Mixing requests with asyncio
When you must use a synchronous HTTP library alongside async code:
1234567891011121314151617181920212223242526272829import asyncio import requests import nest_asyncio nest_asyncio.apply() # Wrapping a blocking requests call for use in asyncio def fetch_post_sync(post_id): response = requests.get( f"https://jsonplaceholder.typicode.com/posts/{post_id}" ) return response.json()["title"] async def fetch_post_async(post_id): loop = asyncio.get_event_loop() title = await loop.run_in_executor(None, fetch_post_sync, post_id) return title async def main(): titles = await asyncio.gather( fetch_post_async(1), fetch_post_async(2), fetch_post_async(3), ) for title in titles: print(title) asyncio.run(main())
Using a Custom Executor
Pass a ThreadPoolExecutor to control the number of threads, or a ProcessPoolExecutor for CPU-bound work:
1234567891011121314151617181920212223242526import asyncio import time from concurrent.futures import ThreadPoolExecutor import nest_asyncio nest_asyncio.apply() # Using a custom thread pool with a fixed size def heavy_computation(value): time.sleep(0.5) # Simulating CPU work return value ** 2 async def main(): loop = asyncio.get_event_loop() with ThreadPoolExecutor(max_workers=4) as executor: results = await asyncio.gather( *[ loop.run_in_executor(executor, heavy_computation, value) for value in range(1, 6) ] ) print(results) asyncio.run(main())
When to Use run_in_executor()
Use it when:
- A third-party library does not support asyncio (e.g.,
requests, legacy database drivers); - File I/O uses standard
open()instead of an async file library; - A CPU-bound function would block the loop for more than a few milliseconds.
Do not use it as a general workaround for writing async code – always prefer native async libraries when they exist.
Tack för dina kommentarer!
Fråga AI
Fråga AI
Fråga vad du vill eller prova någon av de föreslagna frågorna för att starta vårt samtal