# asyncio.gather `asyncio.gather` является удобным способом запуска нескольких корутин параллельно и ожидания их завершения. Функция принимает несколько аргументов - корутин или объектов `Task`, которые нужно запустить, и возвращает объект `Future`, который будет завершен, когда все переданные корутины завершатся. Возвращаемое значение функции `asyncio.gather` - это список с результатами выполнения переданных корутин или объектов `Task`. Результаты возвращаются в том же порядке, в котором переданные корутины были переданы в функцию. Например, если у нас есть две корутины `coroutine_1` и `coroutine_2`, которые должны вернуть значения `result_1` и `result_2` соответственно, мы можем запустить их параллельно и получить результаты с помощью `asyncio.gather`: ```python async def coroutine_1(): # код корутины 1 return result_1 async def coroutine_2(): # код корутины 2 return result_2 results = await asyncio.gather(coroutine_1(), coroutine_2()) ``` После выполнения этого кода переменная `results` будет содержать список `[result_1, result_2]`, где `result_1` - результат выполнения `coroutine_1`, а `result_2` - результат выполнения `coroutine_2`. Также можно использовать `asyncio.gather` для запуска задач, созданных с помощью `asyncio.create_task`. Например: ```python task_1 = asyncio.create_task(coroutine_1()) task_2 = asyncio.create_task(coroutine_2()) results = await asyncio.gather(task_1, task_2) ``` Здесь мы создали две задачи, запустили их с помощью `asyncio.create_task` и дождались их завершения с помощью `asyncio.gather`, который вернул список результатов. # asyncio.TaskGroup `asyncio.TaskGroup` - это контекстный менеджер в модуле `asyncio`, который позволяет группировать несколько задач и управлять ими как единым целым. Он предоставляет удобный интерфейс для создания и запуска задач, а также для ожидания их завершения. Для создания объекта `TaskGroup` необходимо вызвать функцию `asyncio.task_group()`: ```python async with asyncio.task_group() as group: # добавление задач в группу # ожидание завершения всех задач ``` В контекстном менеджере `async with` мы можем добавлять задачи в группу с помощью метода `create_task()`: ```python async with asyncio.task_group() as group: task1 = group.create_task(coroutine1()) task2 = group.create_task(coroutine2()) ``` Здесь мы создаем две задачи и добавляем их в группу с помощью метода `create_task()` объекта `group`. После добавления всех задач в группу, мы можем ожидать завершения всех задач с помощью метода `wait()`: ```python async with asyncio.task_group() as group: task1 = group.create_task(coroutine1()) task2 = group.create_task(coroutine2()) await group.wait() ``` Метод `wait()` вернет управление, когда все задачи в группе завершатся. Также можно использовать метод `cancel_remaining()` для отмены всех оставшихся задач в группе, которые еще не были выполнены: ```python async with asyncio.task_group() as group: task1 = group.create_task(coroutine1()) task2 = group.create_task(coroutine2()) await group.cancel_remaining() ``` Метод `cancel_remaining()` отменяет все оставшиеся задачи в группе, которые еще не завершились, и возвращает `None`. # asyncio.wait_for `asyncio.wait_for` - это функция из модуля `asyncio`, которая позволяет ожидать завершения корутины или объекта `Future` в течение заданного таймаута. Если корутина или `Future` не завершается в течение указанного времени, то возникает исключение `asyncio.TimeoutError`. Синтаксис функции `asyncio.wait_for`: ```python result = await asyncio.wait_for(coro_or_future, timeout, *, loop=None) ``` - `coro_or_future` - это корутина или объект `Future`, который нужно выполнить. - `timeout` - это максимальное время ожидания в секундах. - `loop` - необязательный параметр, позволяющий указать цикл событий, в котором будет выполняться ожидание. Пример использования функции `asyncio.wait_for`: ```python async def long_running_coroutine(): await asyncio.sleep(10) return "result" try: result = await asyncio.wait_for(long_running_coroutine(), timeout=5) print(result) except asyncio.TimeoutError: print("Timeout") ``` Здесь мы запускаем корутину `long_running_coroutine()`, которая выполняется 10 секунд. Мы ожидаем результат ее выполнения с помощью `asyncio.wait_for` и задаем таймаут в 5 секунд. Если корутина не завершится в течение 5 секунд, то будет возбуждено исключение `asyncio.TimeoutError`. В этом примере мы получим исключение `asyncio.TimeoutError`, потому что корутина `long_running_coroutine()` выполняется дольше, чем заданный таймаут. # asyncio.wait `asyncio.wait` - это функция из модуля `asyncio`, которая позволяет ожидать завершения нескольких корутин или объектов `Future`. Она возвращает два множества: множество задач, которые завершились, и множество задач, которые не завершились. Синтаксис функции `asyncio.wait`: ```python done, pending = await asyncio.wait(coroutines_or_futures, timeout=None, return_when=asyncio.ALL_COMPLETED, loop=None) ``` - `coroutines_or_futures` - это список корутин или объектов `Future`, которые нужно выполнить. - `timeout` - это максимальное время ожидания в секундах. Если время ожидания истекло, то будут возвращены текущие результаты. - `return_when` - это условие, которое определяет, когда нужно вернуть результаты. Может принимать следующие значения: - `asyncio.ALL_COMPLETED` (по умолчанию) - вернуть результаты, когда завершатся все задачи. - `asyncio.FIRST_COMPLETED` - вернуть результаты, когда завершится хотя бы одна задача. - `asyncio.FIRST_EXCEPTION` - вернуть результаты, когда завершится первая задача с исключением. - `loop` - необязательный параметр, позволяющий указать цикл событий, в котором будет выполняться ожидание. Функция `asyncio.wait` возвращает два множества: - `done` - множество задач, которые завершились. - `pending` - множество задач, которые еще не завершились. Пример использования функции `asyncio.wait`: ```python async def coroutine1(): await asyncio.sleep(2) return "coroutine1 result" async def coroutine2(): await asyncio.sleep(5) return "coroutine2 result" coroutines = [coroutine1(), coroutine2()] done, pending = await asyncio.wait(coroutines, timeout=None, return_when=asyncio.ALL_COMPLETED) for task in done: print(task.result()) ``` Здесь мы запускаем две корутины `coroutine1()` и `coroutine2()`. Мы ожидаем завершения обеих корутин с помощью `asyncio.wait` и задаем условие `asyncio.ALL_COMPLETED`, чтобы вернуть результаты, когда завершатся все задачи. Мы проходим по множеству `done` и выводим результат каждой завершенной задачи. В этом примере мы получим следующий вывод: ``` coroutine1 result coroutine2 result ``` Таким образом, мы получили результаты выполнения обеих корутин. # `asyncio.wait` VS `asyncio.gather` Как и `asyncio.wait`, `asyncio.gather` также позволяет запускать несколько корутин или объектов `Future`, но есть несколько отличий: 1. `asyncio.gather` более высокоуровневая функция, чем `asyncio.wait`. Она позволяет запустить несколько корутин или объектов `Future` и получить все результаты в единственном объекте `list` в том порядке, в котором корутины или объекты `Future` были переданы в функцию. `asyncio.wait` возвращает два множества: множество задач, которые завершились, и множество задач, которые не завершились. 2. `asyncio.gather` возвращает только список результатов, в то время как `asyncio.wait` возвращает множество задач, которые завершились, и множество задач, которые не завершились. При использовании `asyncio.gather` необходимо обрабатывать исключения внутри корутин или объектов `Future`, чтобы избежать прерывания выполнения остальных корутин в случае возникновения ошибок. 3. `asyncio.gather` прерывает выполнение всех корутин, если одна из корутин выбрасывает исключение. `asyncio.wait` не прерывает выполнение остальных задач, если одна из задач выбрасывает исключение, и требуется явная обработка исключений. Пример использования `asyncio.gather`: ```python import asyncio async def coroutine1(): await asyncio.sleep(2) return "coroutine1 result" async def coroutine2(): await asyncio.sleep(5) return "coroutine2 result" async def main(): results = await asyncio.gather(coroutine1(), coroutine2()) print(results) asyncio.run(main()) ``` Здесь мы используем `asyncio.gather`, чтобы запустить две корутины `coroutine1()` и `coroutine2()`. Мы ждем, пока обе корутины завершатся, и выводим результаты. В этом примере мы получим следующий вывод: ``` ['coroutine1 result', 'coroutine2 result'] ``` Таким образом, `asyncio.gather` возвращает список результатов выполнения корутин в том порядке, в котором они были переданы в функцию. # asyncio.as_completed `asyncio.as_completed` - это функция из модуля `asyncio`, которая возвращает итератор, позволяющий получать завершившиеся корутины в порядке их завершения. Пример использования: ```python import asyncio async def my_coroutine(id, seconds_to_sleep): print(f"Coroutine {id} sleeping for {seconds_to_sleep} seconds...") await asyncio.sleep(seconds_to_sleep) print(f"Coroutine {id} done sleeping.") return f"Coroutine {id} result" async def main(): coroutines = [my_coroutine(1, 3), my_coroutine(2, 2), my_coroutine(3, 1)] for coroutine in asyncio.as_completed(coroutines): result = await coroutine print(result) asyncio.run(main()) ``` В этом примере мы создаем три корутины с разными временами сна. Мы помещаем их в список и передаем этот список в `asyncio.as_completed`. В цикле `for` мы ждем, пока каждая корутина завершится, и выводим результат ее выполнения. Также обратите внимание, что мы используем `await` при вызове каждой корутины, чтобы получить ее результат. Вывод программы будет следующим: ``` Coroutine 3 sleeping for 1 seconds... Coroutine 2 sleeping for 2 seconds... Coroutine 1 sleeping for 3 seconds... Coroutine 3 done sleeping. Coroutine 2 done sleeping. Coroutine 1 done sleeping. Coroutine 3 result Coroutine 2 result Coroutine 1 result ``` Как видно из вывода, `asyncio.as_completed` возвращает корутины в порядке их завершения. Поэтому первым в выводе появился результат выполнения корутины с наименьшим временем сна, затем - корутины с более длительным временем сна. # asyncio.to_thread `asyncio.to_thread` - это функция, которая позволяет запускать синхронный код в отдельном потоке и возвращать результат в асинхронном коде. Пример использования: ```python import asyncio def sync_func(arg1, arg2): print(f"Running sync_func({arg1}, {arg2}) in thread {threading.get_ident()}") return arg1 + arg2 async def async_func(): print(f"Running async_func in event loop {id(asyncio.get_event_loop())}") result = await asyncio.to_thread(sync_func, 1, 2) print(f"Got result {result} in event loop {id(asyncio.get_event_loop())}") asyncio.run(async_func()) ``` В этом примере мы определяем синхронную функцию `sync_func`, которая принимает два аргумента и возвращает их сумму. Мы создаем асинхронную функцию `async_func`, которая вызывает `sync_func` с аргументами 1 и 2, используя `asyncio.to_thread`. Затем мы ждем, пока `to_thread` вернет результат, и выводим его. Вывод программы будет примерно таким: ``` Running async_func in event loop 123456789 Running sync_func(1, 2) in thread 987654321 Got result 3 in event loop 123456789 ``` Как видно из вывода, `to_thread` запускает синхронный код в отдельном потоке, и мы можем продолжать работать с асинхронным кодом в ожидании завершения этого потока. Обратите внимание, что `to_thread` возвращает `awaitable` объект, поэтому мы можем использовать `await`, чтобы дождаться завершения запущенного потока. Использование `asyncio.to_thread` может быть полезным, когда вам нужно выполнить синхронный код, который может блокировать основной поток событий, например, ввод-вывод операции, которые не могут быть выполнены асинхронно. Однако, стоит учитывать, что создание потоков может вызвать накладные расходы на производительность, поэтому стоит использовать `asyncio.to_thread` с умеренной осторожностью. # Исключения Как работать с исключениями ``` import asyncio async def my_coroutine(name): print(f"Coroutine {name} started") await asyncio.sleep(1) if name == "second": raise ValueError(f"Error in {name} coroutine") print(f"Coroutine {name} finished") async def main(): tasks = [asyncio.create_task(my_coroutine(name)) for name in ["first", "second", "third"]] done, pending = await asyncio.wait(tasks, return_when=asyncio.ALL_COMPLETED) for task in done: if task.exception(): print(f"Coroutine {task.get_name()} finished with error: {task.exception()}") else: print(f"Coroutine {task.get_name()} finished") asyncio.run(main()) ```