Используя asyncio, каков самый простой способ дождаться всех результатов и дополнительно получить их обратно?

#python #python-asyncio

#python #python-asyncio

Вопрос:

У меня есть долго работающая функция, которая вызывается несколько раз for циклом с разными параметрами.

Я хочу оценить asyncio библиотеку, чтобы легко увеличить скорость этой функции.

Функция возвращает определенное значение, которое должно храниться на уровне for цикла в списке resultList .

Я уже нашел несколько фрагментов кода, которые, похоже, соответствуют моему требованию «простоты реализации» (мне просто нужно добавить @background декоратор), но мне не хватает двух функций:

  1. как дождаться на уровне for цикла, пока не будут выполнены все выполнения функции?
  2. как собрать результаты вызова функции на уровне for цикла?

Как мне расширить этот фрагмент кода?

 import asyncio
import time

def background(f):
    def wrapped(*args, **kwargs):
        return asyncio.get_event_loop().run_in_executor(None, f, *args, **kwargs)

    return wrapped

@background
def your_function(argument):
    time.sleep(5)
    myReturn = 'function finished for '   str(argument)
    print(myReturn)
    return  myReturn


if __name__ == '__main__':
    resultList = []

    for i in range(10):
        your_function(i)
        # resultList.append() 


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

    print('this should only be printed once all calls to your_function() are finished')
    print("resultList=", resultList) # should contain a list of entries with 'function finished for x' 
  

Комментарии:

1. Ограничен ли ваш процесс ввода-вывода? В противном asyncio случае это вообще не поможет, поскольку GIL все еще активен, и только один поток может обрабатывать код Python одновременно. Фрагмент кода, похоже, вообще не выиграет от этого. Я знаю, что это не отвечает на вопрос, но я бы хотел, чтобы вы не шли по ненужному пути.

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

Ответ №1:

Есть несколько проблем, которые я вижу с кодом, которым вы поделились. Примечание: мой ответ основан на вашем комментарии к Jan, где вы упоминаете, что ваша функция ограничена вводом-выводом.

  1. Не используйте asyncio.get_event_loop().run_in_executor() , используйте asyncio.run() в своей точке входа и сохраняйте свои методы полностью асинхронными, если можете. (https://docs.python.org/3/library/asyncio-task.html#asyncio.run )
  2. Вы используете time.sleep() . Это синхронная версия sleep, которая не будет имитировать asyncio. Используйте asyncio.sleep() вместо этого.
  3. Обратите внимание, что run_in_executor возвращает объект future, который вы хотите await . Поскольку вы запускаете свой код в синхронной функции, это более сложно, чем запускать все это из асинхронной функции.

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

 import asyncio

async def your_function(argument):
    await asyncio.sleep(5)  # note the await here (compared to time.sleep())
    my_return = f"function finished for {argument}"
    print(my_return)
    return my_return


async def async_main():
    result_list = await asyncio.gather(*(range(10)))  # gather will run it all concurrently
    print("this should only be printed once all calls to your_function() are finished")
    print(f"result_list={result_list}", result_list) # should contain a list of entries with 'function finished for x'

if __name__ == '__main__':
    asyncio.run(async_main())