Как сделать функцию, включающую неблокирование цикла for? - PullRequest
0 голосов
/ 22 января 2019

Я пытаюсь сделать приведенный ниже код асинхронным:

import asyncio
import random

async def count():
    l = []
    for i in range(10000000):
        l.append(i)
    return random.choice(l)

async def long_task1():
    print('Starting task 1...')
    task_output = await count()
    print('Task 1 output is {}'.format(task_output ))


async def long_task2():
    print('Starting task 2...')
    task_output = await count()
    print('Task 2 output is {}'.format(task_output ))

async def main():
    await asyncio.gather(long_task1(), long_task2())

if __name__ == '__main__':
    asyncio.get_event_loop().run_until_complete(main())

В настоящее время он будет работать синхронно.

Это потому, что в функции count отсутствует оператор await?

Я попытался переделать функцию, чтобы включить await:

async def count():
    l = []
    for i in range(10000000):
        l.append(i)
    choice = await random.choice(l)
    return choice

и он запустится асинхронно (и Starting task 1..., и Starting task 2... будут напечатаны один за другим), но затем я получаю сообщение об ошибке:

TypeError: объект int нельзя использовать в выражении 'await'

Я понимаю, что ошибка произошла, потому что результат random.choice(l) не является ожидаемым (сопрограммой), но я не знаю, как это исправить, не бегая по кругу. Нужно ли мне как-то рефакторировать цикл for в программу самостоятельно?

Ответы [ 3 ]

0 голосов
/ 22 января 2019

Это потому, что в функции подсчета отсутствует оператор await?

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

Для принудительного временного приостановления , вы можете добавить await asyncio.sleep(0) внутри цикла в count.Добавление await перед обычной функцией, такой как random.choice, не работает, потому что await требует объект, который реализует ожидаемый интерфейс, а в вашем коде random.choice просто возвращает целое число.

0 голосов
/ 24 января 2019

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

Но в реальном мире, если у вас есть задача с интенсивным использованием процессора, у вас есть два варианта

  1. Использовать многопроцессорность и делегировать задачу другому процессу.
  2. Используйте привязку собственного кода, которая освобождает GIL и использует потоки.

Как следует из названия, библиотека предназначена для асинхронного ввода-вывода. async"io"

0 голосов
/ 22 января 2019

Ваш код вызывает gather, который одновременно запускает long_task1 и long_task2. Затем вы вызываете ожидание на count в каждой функции. Но на этой подпрограмме await закончится. Таким образом, общая подпрограмма будет завершена до начала следующей подпрограммы. Вам нужна функция, чтобы приостановить всю задачу. Я создал два способа обойти это. Оба связаны с созданием новых задач.

Создание новой подпрограммы:

async def count():
   l = []
   await asyncio.wait_for(loopProcess(l), timeout=1000000.0)
   return random.choice(l)

async def loopProcess(l):
   for i in range(10000000):
      l.append(i)

Вы также можете оставить свою функцию count такой же, как и в исходном коде, и изменить свой long_task(1/2) следующим образом, чтобы сделать count() новой задачей:

async def long_task1():
   print('Starting task 1...')
   task_output = await asyncio.shield(count())
   print('Task 1 output is {}'.format(task_output ))


async def long_task2():
   print('Starting task 2...')
   task_output = await asyncio.shield(count())
   print('Task 2 output is {}'.format(task_output ))

Вы также можете использовать create_task, если у вас есть Python 3.7.

Источник: https://docs.python.org/3/library/asyncio-task.html

...