Как правильно переключаться между асинхронными задачами? - PullRequest
0 голосов
/ 06 сентября 2018

Предположим, у меня есть некоторые задачи, выполняющиеся асинхронно. Они могут быть полностью независимыми, но я все же хочу установить точки, где такты будут приостанавливаться, чтобы они могли работать одновременно.

Как правильно выполнять задачи одновременно? В настоящее время я использую await asyncio.sleep(0), но я чувствую, что это добавляет много накладных расходов.

import asyncio

async def do(id, amount):
    for i in range(amount):
        # Do some time-expensive work
        print(f'{id}: has done {i}')

        await asyncio.sleep(0)

    return f'{id}: done'

async def main():
    res = await asyncio.gather(do('Task1', 5), do('Task2', 3))
    print(*res, sep='\n')

loop = asyncio.get_event_loop()

loop.run_until_complete(main())

выход

Task1: has done 0
Task2: has done 0
Task1: has done 1
Task2: has done 1
Task1: has done 2
Task1: done
Task2: done

Если бы мы использовали простые генераторы, пустой yield приостановил бы выполнение задачи без каких-либо издержек, но пустой await недопустим.

Как правильно установить такие контрольные точки без издержек?

1 Ответ

0 голосов
/ 06 сентября 2018

Как упомянуто в комментариях, обычно сопрограммы asyncio автоматически приостанавливают вызовы, которые блокируют или спят в эквивалентном синхронном кофе.В вашем случае сопрограмма привязана к процессору, поэтому ожидания блокирующих вызовов недостаточно, необходимо время от времени передавать управление циклу событий, чтобы разрешить запуск остальной системы.

Явные выходы нередки всовместная многозадачность, и использование await asyncio.sleep(0) для этой цели будет работать как задумано , это несет в себе риск: слишком часто спите, и вы замедляете вычисления из-за ненужных переключателей;спите слишком редко, и вы замыкаете цикл обработки событий, проводя слишком много времени в одной сопрограмме.

Решение, предлагаемое asyncio, состоит в том, чтобы разгрузить код, связанный с ЦП, в пул потоков, используя run_in_executor.Ожидание этого автоматически приостановит сопрограмму, пока задача интенсивного использования процессора не будет сделана, без какого-либо промежуточного опроса.Например:

import asyncio

def do(id, amount):
    for i in range(amount):
        # Do some time-expensive work
        print(f'{id}: has done {i}')

    return f'{id}: done'

async def main():
    loop = asyncio.get_event_loop()
    res = await asyncio.gather(
        loop.run_in_executor(None, do, 'Task1', 5),
        loop.run_in_executor(None, do, 'Task2', 3))
    print(*res, sep='\n')

loop = asyncio.get_event_loop()
loop.run_until_complete(main())
...