Начать обработку асинхронных задач при добавлении их в цикл событий - PullRequest
0 голосов
/ 26 июня 2018

Обычный шаблон с asyncio, подобный показанному здесь , состоит в добавлении коллекции сопрограмм в список, а затем asyncio.gather их.

Например:

async def some_task(i):
    # Do something asynchronously with i

tasks = [some_task(i) for i in range(100)]

loop.run_until_complete(asyncio.gather(**tasks))

Здесь порядок выполнения этого кода таков, что ни одна из задач не выполняется, пока мы формируем список. Мы добавляем задачу 1 в список, затем задачу 2 и т. Д. И , затем добавляем задачи 1-100 в цикл событий.


Однако я хочу, чтобы само создание задачи было частью цикла событий. Я хочу, чтобы задача 1 планировалась сразу после ее создания, а затем, когда задача ожидает чего-то в другом потоке, вернитесь к созданию задачи и создайте задачу 2 и добавьте ее в цикл событий.

Полагаю, это улучшит параллелизм моего асинхронного кода. Это возможно?


Например, моей первой мыслью было бы поместить создание задач в сопрограмму и планировать задачи по мере их создания:

async def some_task(i):
    # Do something asynchronously with i

async def generate_tasks(loop):
    tasks = []
    for i in range(100):
        task = loop.create_task(some_task(i))
        tasks.append(loop)
    await asyncio.gather(**tasks)

loop.run_until_complete(generate_tasks())

Однако, поскольку мой generate_tasks никогда не использует await, выполнение никогда не передается обратно в цикл обработки событий, поэтому весь generate_tasks будет выполняться до того, как some_task() будет запущен вообще.


Но тогда, если я await выполню каждую задачу по мере ее создания, она будет ждать завершения каждой задачи, прежде чем перейти к следующей задаче, не давая мне никакого параллелизма вообще!

async def generate_tasks(loop):
    tasks = []
    for i in range(100):
        await some_task(i)

loop.run_until_complete(generate_tasks())

1 Ответ

0 голосов
/ 26 июня 2018

Однако, поскольку мой generate_tasks никогда не использует await, выполнение никогда не передается обратно в цикл обработки событий

Вы можете использовать await asyncio.sleep(0) для принудительной сдачи в цикл событий внутри for. Но это вряд ли что-то изменит, создание пары «задача / сопрограмма» действительно эффективно.

Прежде чем оптимизировать это, измерьте (с помощью чего-то столь же простого, как time.time, если необходимо), сколько времени требуется для выполнения понимания списка [some_task(i) for i in range(100)]. Затем подумайте, будет ли какое-то различие для вашего приложения иметь разброс этого времени (возможно, для его завершения потребуется больше из-за увеличения затрат на планирование). Результаты могут вас удивить.

...