# 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())
```