Python Asyncio Await Tasks - PullRequest
       6

Python Asyncio Await Tasks

0 голосов
/ 11 апреля 2019

Необходимость: Python 3.7 или более поздняя версия.

Две функции main1 и main2 определены ниже.Один создает задачи и затем ждет их всех в конце;Каждый другой создает и ждет каждый раз.

В то время как main1 занимает 2 секунды, а main2 - 30 секунд.Почему?

import asyncio

async def say_after(delay, what):
    await asyncio.sleep(delay)
    print(what)

async def main1():
    tasks = []
    for _ in range(10):
        task1 = asyncio.create_task(say_after(1, 'hello'))
        task2 = asyncio.create_task(say_after(2, 'world'))
        tasks.append(task1)
        tasks.append(task2)
    for x in tasks:
        await x

async def main2():
    for _ in range(10):
        await asyncio.create_task(say_after(1, 'hello'))
        await asyncio.create_task(say_after(2, 'world'))

asyncio.run(main2())

РЕДАКТ. 1:

Вот версия main3, которая занимает 20 секунд.Я бы сказал, что все это просто вне интуиции: (

async def main3():
    for _ in range(10):
        task1 = asyncio.create_task(say_after(1, 'hello'))
        task2 = asyncio.create_task(say_after(2, 'world'))
        await task1
        await task2

РЕДАКТИРОВАТЬ 2:

(с еще несколькими примерами кода, добавленными ниже)прочитайте подробные ответы от @freakish, я все еще застрял в одном месте: так что только непрерывный await будет корпоративно работать параллельно (main4)?

Так как create_task() не требует времени (верно)?), почему бы не два await в main5 работать в фоновом режиме, чтобы main5 занимал максимальное время (task1, task2)?

Это механизм await по своему замыслу или простоasyncio ограничение (в дизайне или в реализации)?

И любые await подробные поведения, определенные в официальных документах Python?

# took 2 seconds
async def main4():
    task1 = asyncio.create_task(say_after(1, 'hello'))
    task2 = asyncio.create_task(say_after(2, 'world'))
    await task1
    await task2

# took 3 seconds
async def main5():
    task1 = asyncio.create_task(say_after(1, 'hello'))
    await task1
    task2 = asyncio.create_task(say_after(2, 'world'))
    await task2

1 Ответ

3 голосов
/ 11 апреля 2019

Поскольку main1 создает всех задач одновременно, а затем ожидает всех из них после их создания. Все происходит параллельно. И поэтому общее время составляет максимум всех времен, что составляет 2 с.

Пока main2 создает новое задание только после завершения предыдущего. Все происходит последовательно. Таким образом, общее время составляет сумма всех времен, которое (судя по коду) должно быть 30 с.

Редактировать: скажем, у вас есть 3 задачи: task1, task2, task3. Если вы делаете

  1. создать задачу1
  2. Ожидание задачи1
  3. создать задачу2
  4. жду задание2
  5. создать задачу3
  6. ожидание задачи3

тогда общее время выполнения, очевидно, составляет task1.time + task2.time + task3.time, потому что нет фоновой обработки. Поток последовательный. Теперь допустим, что вы делаете

  1. создать задачу1
  2. создать задачу2
  3. создать задачу3
  4. Ожидать задание1
  5. Ожидать задание2
  6. Ожидание задачи3

Теперь task1, task2, task3 работает в фоновом режиме . Таким образом, для обработки 4 требуется T1 = task1.time. Но для pt 5 требуется T2 = max(task2.time - T1, 0) для его обработки, потому что он уже работал в фоновом режиме в течение T1 времени. На pt 6 требуется T3 = max(task3.time - T2 - T1, 0) для его обработки, потому что он уже работал в фоновом режиме в течение T1+T2 времени. Теперь некоторые математические вычисления необходимы для вычисления этой суммы T1+T2+T3=max(task1.time, task2.time, task3.time).

Но интуиция такова: если taskX был самым длинным и он закончил, то все остальное закончилось из-за параллельной обработки. Таким образом, await немедленно возвращается, делая максимальное время обработки максимальным за все время.

Примечание: есть нюансы: это работает только тогда, когда вы действительно делаете параллелизуемые вещи, такие как asyncio.sleep(). Если эти задачи являются синхронными (скажем, некоторые вычисления процессора), то оба случая дадут 30 с.

Edit2: Так что ваш main3 имеет немного другой поток. Это позволяет двум задачам работать параллельно. Но не более:

  1. создать задачу1
  2. создать задачу2
  3. await task1
  4. ожидание задачи2
  5. создать задачу3
  6. создать задачу4
  7. Ожидание задачи3
  8. Ожидать задание4

Так что на этот раз task1 и task2 происходят параллельно. Но только после того, как они сделаны, task3 и task4 могут работать. В параллели. Таким образом, для каждой группы общее время является максимальным, но вы должны суммировать отдельные группы. То есть общее время выполнения составляет max(task1.time, task2.time)+max(task3.time, task4.time), что в вашем случае составляет

max(1,2) + ... + max(1,2) [10 times] = 20
...