Convert Blocking python function to async function

Question:

So currently I have 4 api requests which are called in synchronous method using a third_party lib which is synchronous. What i want is to run them in parallel. So that my total time to call all 4 apis is reduced.

I am using fastapi as micro framework.

utilities.py

async def get_api_1_data():
    data = some_third_party_lib()
    return data

async def get_api_2_data():
    data = some_third_party_lib()
    return data

async def get_api_3_data():
    data = some_third_party_lib()
    return data

async def get_api_4_data():
    data = some_third_party_lib()
    return data

my main.py looks something like this

import asyncio
from fastapi import FastAPI
app = FastAPI()

@app.get("/")
async def fetch_new_exposure_api_data(node: str):
   functions_to_run = [get_api_1_data(), get_api_2_data(), get_api_3_data(), get_api_4_data()]
   r1, r2, r3, r4 = await asyncio.gather(*functions_to_run)
   return [r1, r2, r3, r4]

So the issue is i can not put await in front of some_third_party_lib() as it’s not a async lib it’s a sync lib.
So is there any way i can convert them to async functionality to run them in parallel.

Asked By: Abhishek Sachan

||

Answers:

Unfortunately, you cannot make a synchronous function asynchronous without changing its implementation. If some_third_party_lib() is a synchronous function, you cannot make it run in parallel using the asyncio library.

One workaround is to run each of the calls to some_third_party_lib() in a separate thread. You can use the concurrent.futures library in Python to create and manage a pool of worker threads. Here’s an example setup that I came up with:

import concurrent.futures

def get_api_1_data():
    return some_third_party_lib()

def get_api_2_data():
    return some_third_party_lib()

def get_api_3_data():
    return some_third_party_lib()

def get_api_4_data():
    return some_third_party_lib()

@app.get("/")
async def fetch_new_exposure_api_data(node: str):
    with concurrent.futures.ThreadPoolExecutor() as executor:
        results = [executor.submit(fn) for fn in [get_api_1_data, get_api_2_data, get_api_3_data, get_api_4_data]]

    return [result.result() for result in concurrent.futures.as_completed(results)]

This way, each call to some_third_party_lib() will run in a separate thread and they will run in parallel. Note that the order of the results in the returned list may not match the order of the functions in the functions_to_run list.

Answered By: Kamuffel