Search code examples
python-3.xloopspython-asyncioaiohttphttpx

Make an async HTTP request and continue executing the loop


I have a simple python function with the following pseudo-code:

while True:

    # 1 - CPU Intensive calculations(Synchronous)
    
    
    # 2 - Take the result from the synchronous calculation and POST it to a server

The while loop runs forever and does CPU intensive calculations for the first half of the loop. All of that is run synchronously and that's alright. What I'd like to do is shave off some time by making the POST request asynchronous. The while loop needs to run forever, however I'd like the request to be made asynchronously so that the loop can continue with the next iteration without waiting for the request to resolve.

Would like to know what is the best way to achieve this with asyncio without using any extra threads/greenlets/processes

Edit


Before posting the question here, I have tried this approach:

async def example_function():
    # Synchronous CPU Intensive calculations

    client = httpx.AsyncClient()
    # Make a call to the REST API
    await client.post()


async def main():
    while True:
        await asyncio.gather(example_function(), example_function())


if __name__ == "__main__":
    asyncio.run(main())

I am completely new to asynchronous programming with python. I tried the gather method, however the flaw with my implementation approach is that the second iteration of the example_function() does not make the POST req asynchronously. I understand that the asyncio.gather() basically schedules a task for each of the functions passed to it, and I one of the tasks awaits, it continues with execution of the next one. However, I need to run the example_function() in a loop forever and not just n times


Solution

  • Would like to know what is the best way to achieve this with asyncio without using any extra threads/greenlets/processes

    If your code is synchronous, you will need to use some form of multi-threading, but you can do it in a clean way through the thread pool provided by asyncio for that purpose. For example, if you run your sync code through loop.run_in_executor(), the event loop will keep spinning while the calculations are running, and the tasks placed in the background will be serviced. This allows you to use asyncio.create_task() to run the second part of the loop in the background or, more precisely, in parallel with the rest of the event loop.

    def calculations(arg1):
        # ... synchronous code here
    
    async def post_result(result, session):
        async with session.post('http://httpbin.org/post',
                                data={'key1': result}) as resp:
            resp.raise_for_status()
    
    async def main():
        loop = asyncio.get_event_loop()
    
        with aiohttp.ClientSession() as session:
            while True:
                # run the sync code off-thread so the event loop
                # continues running, but wait for it to finish
                result = await loop.run_in_executor(None, calculations, arg1)
    
                # don't await the task, let it run in the background
                asyncio.create_task(post_result(result, session))
    
    if __name__ == '__main__':
        asyncio.run(main())