@Jolt

Как тригернуть корутину и продолжить синхронный код?

Задача довольно тривиальная как по мне, но не могу понять как правильно это сделать.

Предположим на сервер приходит запрос от пользователя.
Необходимо асинхронно залезть в базу и синхронно что-то посчитать.

Но если выполнить код ниже, он очевидно сначала будет ждать возвращения функции async_io, а только потом начнет компутинг.

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()


В идеале я бы хотел в функции async_io дойти до места где происходит await, и потом начать синхронный sync_calculating.
Соответственно к концу подсчетов у меня уже вернулись бы данные из базы и осталось их только вернуть.

Нечто подобное можно сделать если переписать task примерно так:
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


То есть планировщик добавит таск async_io в очередь, и когда дойдет до await asyncio.sleep он вызовет async_io.
Но как-то очень криво.
res_async = await res_async нужен для того чтобы убедиться что корутина выполнилась к этому моменту, и подождать её в противном случае

Нельзя как-то триггернуть выполнение async_io, дойти до await внутри него и вернуться выполнять синхронный код?
  • Вопрос задан
  • 125 просмотров
Решения вопроса 1
Сразу скажу, что как вы описали - не заработает. Любой синхронный код заблокирует выполнение event_loop, поэтому в асинхронном коде следует избегать использование синхронных вызовов и функций.

Желаемого можно добиться, разбив функцию async_io на две части - первую до запроса к БД, а вторую - выполняющую запрос к БД. Тогда запрос к БД можно выполнить одновременно с расчетами через 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()
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы