J
J
Jolt2021-06-23 22:57:12
Python
Jolt, 2021-06-23 22:57:12

How to trigger a coroutine and continue synchronous code?

The task is quite trivial as for me, but I can not figure out how to do it correctly.

Let's assume on the server the request from the user comes.
It is necessary to asynchronously get into the database and synchronously calculate something.

But if you run the code below, it will obviously first wait for the async_io function to return, and only then start computing.

async def async_io():
    print("async_io start")
    result = await asyncio.sleep(1, result=500)
    print("async_io end")
    return result


def sync_calculating():
    print("sync_calculating start")
    res = 0
    for i in range(1000):
        res += 1
    print("sync_calculating end")
    return res


async def task():
    print("Task start")
    res_async = await async_io()
    res_sync = sync_calculating()
    print("Task end")
    return res_async + res_sync


loop = asyncio.get_event_loop()
loop.create_task(task())
loop.run_forever()


Ideally, in the async_io function, I would like to get to the place where await occurs, and then start synchronous sync_calculating.
Accordingly, by the end of the calculations, I would have returned the data from the database and it remains only to return them.

Something similar can be done by rewriting task like this:
async def task():
    print("Task start")
    res_async = asyncio.ensure_future(async_io())
    await asyncio.sleep(0.1)
    res_sync = sync_calculating()
    res_async = await res_async
    print("Task end")
    return res_async + res_sync


That is, the scheduler will add the async_io task to the queue, and when it reaches await asyncio.sleep it will call async_io.
But somehow very crooked.
res_async = await res_async is needed in order to make sure that the coroutine has completed by this moment, and wait for it otherwise.

Can't we somehow trigger the execution of async_io, reach the await inside it and return to execute the synchronous code?

Answer the question

In order to leave comments, you need to log in

1 answer(s)
D
Dmitry Shitskov, 2021-06-23
@Jolt

I must say right away that as you described - it will not work. Any synchronous code will block the execution of the event_loop, so you should avoid using synchronous calls and functions in asynchronous code.
You can achieve what you want by breaking the async_io function into two parts - the first one before the database request, and the second one, which executes the database request. Then the query to the database can be executed simultaneously with the calculations via asyncio.gather

import asyncio

async def async_io():
    print("async_io start")
    result = 100
    print("async_io end")
    return result
    
async def async_db_query(data):
    print("async_db_query start")
    result = await asyncio.sleep(1, result=500)
    print("async_db_query end")
    return result


async def async_calculating(data):
    print("async_calculating start")
    res = 0
    for i in range(1000):
        res += 1
    print("async_calculating end")
    return res


async def task():
    print("Task start")
    res_async = await async_io()
    res = await asyncio.gather(
        async_db_query(res_async), 
        async_calculating(res_async)
        )
    task_calc = sum(res)
    print(f"Task end. Result: {task_calc}")
    
    return task_calc


loop = asyncio.get_event_loop()
loop.create_task(task())
loop.run_forever()

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question