> **Источник:** https://python-all.ru/3.6/library/asyncio-dev.html
>
> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.

---

# 18.5.9. Разработка с asyncio

Асинхронное программирование отличается от классического «последовательного» программирования. На этой странице перечислены типичные ловушки и объясняется, как их избежать.

## 18.5.9.1. Режим отладки asyncio

Реализация [`asyncio`](https://python-all.ru/3.6/library/asyncio.html#module-asyncio) написана с расчётом на производительность. Чтобы упростить разработку асинхронного кода, можно включить *режим отладки*.

Чтобы включить все проверки отладки для приложения:

- Включите глобальный режим отладки asyncio, установив переменную окружения [`PYTHONASYNCIODEBUG`](https://python-all.ru/3.6/using/cmdline.html#envvar-PYTHONASYNCIODEBUG) в `1`, или вызвав [`AbstractEventLoop.set_debug()`](https://python-all.ru/3.6/library/asyncio-eventloop.html#asyncio.AbstractEventLoop.set_debug).
- Установите уровень журналирования [логгера asyncio](https://python-all.ru/3.6/library/asyncio-dev.html#asyncio-logger) в `logging.DEBUG`. Например, вызовите `logging.basicConfig(level=logging.DEBUG)` при запуске.
- Настройте модуль [`warnings`](https://python-all.ru/3.6/library/warnings.html#module-warnings) для отображения предупреждений [`ResourceWarning`](https://python-all.ru/3.6/library/exceptions.html#ResourceWarning). Например, используйте параметр командной строки `-Wdefault` Python, чтобы показать их.

Примеры проверок отладки:

- Журналируйте [корутины, которые определены, но никогда не вызывались через yield from](https://python-all.ru/3.6/library/asyncio-dev.html#asyncio-coroutine-not-scheduled)
- Методы [`call_soon()`](https://python-all.ru/3.6/library/asyncio-eventloop.html#asyncio.AbstractEventLoop.call_soon) и [`call_at()`](https://python-all.ru/3.6/library/asyncio-eventloop.html#asyncio.AbstractEventLoop.call_at) вызывают исключение, если их вызвать из неправильного потока.
- Журналируйте время выполнения селектора
- Журналируйте колбэки, выполнение которых занимает более 100 мс. Атрибут `AbstractEventLoop.slow_callback_duration` задаёт минимальную продолжительность в секундах для «медленных» колбэков.
- Предупреждения [`ResourceWarning`](https://python-all.ru/3.6/library/exceptions.html#ResourceWarning) выводятся, когда транспорты и циклы событий [не закрыты явно](https://python-all.ru/3.6/library/asyncio-dev.html#asyncio-close-transports).

> **См. также**
>
> Метод [`AbstractEventLoop.set_debug()`](https://python-all.ru/3.6/library/asyncio-eventloop.html#asyncio.AbstractEventLoop.set_debug) и [логгер asyncio](https://python-all.ru/3.6/library/asyncio-dev.html#asyncio-logger).

## 18.5.9.2. Отмена

Отмена задач не является распространённой в классическом программировании. В асинхронном программировании это не только обычное явление, но и необходимо подготовить свой код к её обработке.

Future и задачи могут быть отменены явно с помощью их метода [`Future.cancel()`](https://python-all.ru/3.6/library/asyncio-task.html#asyncio.Future.cancel). Функция [`wait_for()`](https://python-all.ru/3.6/library/asyncio-task.html#asyncio.wait_for) отменяет ожидаемую задачу при наступлении тайм-аута. Существует много других случаев, когда задача может быть отменена косвенно.

Не вызывайте метод [`set_result()`](https://python-all.ru/3.6/library/asyncio-task.html#asyncio.Future.set_result) или [`set_exception()`](https://python-all.ru/3.6/library/asyncio-task.html#asyncio.Future.set_exception) объекта [`Future`](https://python-all.ru/3.6/library/asyncio-task.html#asyncio.Future), если future отменён: это приведёт к исключению. Например, напишите:

```python
if not fut.cancelled():
    fut.set_result('done')
```

Не планируйте напрямую вызов метода [`set_result()`](https://python-all.ru/3.6/library/asyncio-task.html#asyncio.Future.set_result) или [`set_exception()`](https://python-all.ru/3.6/library/asyncio-task.html#asyncio.Future.set_exception) объекта future с помощью [`AbstractEventLoop.call_soon()`](https://python-all.ru/3.6/library/asyncio-eventloop.html#asyncio.AbstractEventLoop.call_soon): future может быть отменён до того, как его метод будет вызван.

Если вы ожидаете future, следует заранее проверить, не был ли future отменён, чтобы избежать бесполезных операций. Пример:

```python
@coroutine
def slow_operation(fut):
    if fut.cancelled():
        return
    # ... медленное вычисление ...
    yield from fut
    # ...
```

Функция [`shield()`](https://python-all.ru/3.6/library/asyncio-task.html#asyncio.shield) также может использоваться для игнорирования отмены.

## 18.5.9.3. Конкурентность и многопоточность

Цикл событий работает в одном потоке и выполняет все колбэки и задачи в том же потоке. Пока задача выполняется в цикле событий, никакая другая задача не выполняется в том же потоке. Но когда задача использует `yield from`, она приостанавливается, и цикл событий выполняет следующую задачу.

Чтобы запланировать колбэк из другого потока, следует использовать метод [`AbstractEventLoop.call_soon_threadsafe()`](https://python-all.ru/3.6/library/asyncio-eventloop.html#asyncio.AbstractEventLoop.call_soon_threadsafe). Пример:

```python
loop.call_soon_threadsafe(callback, *args)
```

Большинство объектов asyncio не являются потокобезопасными. Стоит беспокоиться только при доступе к объектам за пределами цикла событий. Например, чтобы отменить future, не вызывайте напрямую его метод [`Future.cancel()`](https://python-all.ru/3.6/library/asyncio-task.html#asyncio.Future.cancel), а:

```python
loop.call_soon_threadsafe(fut.cancel)
```

Для обработки сигналов и выполнения подпроцессов цикл событий должен выполняться в главном потоке.

Чтобы запланировать объект корутины из другого потока, следует использовать функцию [`run_coroutine_threadsafe()`](https://python-all.ru/3.6/library/asyncio-task.html#asyncio.run_coroutine_threadsafe). Она возвращает [`concurrent.futures.Future`](https://python-all.ru/3.6/library/concurrent.futures.html#concurrent.futures.Future) для доступа к результату:

```python
future = asyncio.run_coroutine_threadsafe(coro_func(), loop)
result = future.result(timeout)  # Ожидание результата с тайм-аутом
```

Метод [`AbstractEventLoop.run_in_executor()`](https://python-all.ru/3.6/library/asyncio-eventloop.html#asyncio.AbstractEventLoop.run_in_executor) можно использовать с исполнителем пула потоков, чтобы выполнить колбэк в другом потоке и не блокировать поток цикла событий.

> **См. также**
>
> Раздел [Примитивы синхронизации](https://python-all.ru/3.6/library/asyncio-sync.html#asyncio-sync) описывает способы синхронизации задач.
>
> Раздел [Подпроцессы и потоки](https://python-all.ru/3.6/library/asyncio-subprocess.html#asyncio-subprocess-threads) перечисляет ограничения asyncio для запуска подпроцессов из разных потоков.

## 18.5.9.4. Правильная обработка блокирующих функций

Блокирующие функции нельзя вызывать напрямую. Например, если функция блокируется на 1 секунду, другие задачи задерживаются на 1 секунду, что может существенно повлиять на отзывчивость.

Для работы с сетью и подпроцессами модуль [`asyncio`](https://python-all.ru/3.6/library/asyncio.html#module-asyncio) предоставляет высокоуровневые API, такие как [протоколы](https://python-all.ru/3.6/library/asyncio-protocol.html#asyncio-protocol).

Исполнитель можно использовать для запуска задачи в другом потоке или даже в другом процессе, чтобы не блокировать поток цикла событий. См. метод [`AbstractEventLoop.run_in_executor()`](https://python-all.ru/3.6/library/asyncio-eventloop.html#asyncio.AbstractEventLoop.run_in_executor).

> **См. также**
>
> Раздел [Отложенные вызовы](https://python-all.ru/3.6/library/asyncio-eventloop.html#asyncio-delayed-calls) подробно описывает, как цикл событий обрабатывает время.

## 18.5.9.5. Логирование

Модуль [`asyncio`](https://python-all.ru/3.6/library/asyncio.html#module-asyncio) регистрирует информацию с помощью модуля [`logging`](https://python-all.ru/3.6/library/logging.html#module-logging) в логгере `'asyncio'`.

Уровень логирования по умолчанию для модуля [`asyncio`](https://python-all.ru/3.6/library/asyncio.html#module-asyncio) – `logging.INFO`. Для тех, кто не хочет такой подробности от [`asyncio`](https://python-all.ru/3.6/library/asyncio.html#module-asyncio), уровень можно изменить. Например, чтобы установить уровень `logging.WARNING`:

```text
logging.getLogger('asyncio').setLevel(logging.WARNING)
```

## 18.5.9.6. Обнаружение объектов корутин, которые никогда не планировались

Когда вызывается корутинная функция и её результат не передаётся в [`ensure_future()`](https://python-all.ru/3.6/library/asyncio-task.html#asyncio.ensure_future) или в метод [`AbstractEventLoop.create_task()`](https://python-all.ru/3.6/library/asyncio-eventloop.html#asyncio.AbstractEventLoop.create_task), выполнение объекта корутины никогда не будет запланировано, что, скорее всего, является ошибкой. [Включите отладочный режим asyncio](https://python-all.ru/3.6/library/asyncio-dev.html#asyncio-debug-mode), чтобы [зарегистрировать предупреждение](https://python-all.ru/3.6/library/asyncio-dev.html#asyncio-logger) для его обнаружения.

Пример с ошибкой:

```python
import asyncio

@asyncio.coroutine
def test():
    print("never scheduled")

test()
```

Вывод в режиме отладки:

```python
Coroutine test() at test.py:3 was never yielded from
Coroutine object created at (most recent call last):
  File "test.py", line 7, in <module>
    test()
```

Исправление заключается в вызове функции [`ensure_future()`](https://python-all.ru/3.6/library/asyncio-task.html#asyncio.ensure_future) или метода [`AbstractEventLoop.create_task()`](https://python-all.ru/3.6/library/asyncio-eventloop.html#asyncio.AbstractEventLoop.create_task) с объектом корутины.

> **См. также**
>
> [Уничтожение ожидающей задачи](https://python-all.ru/3.6/library/asyncio-dev.html#asyncio-pending-task-destroyed).

## 18.5.9.7. Обнаружение непоглощённых исключений

Python обычно вызывает [`sys.excepthook()`](https://python-all.ru/3.6/library/sys.html#sys.excepthook) для необработанных исключений. Если [`Future.set_exception()`](https://python-all.ru/3.6/library/asyncio-task.html#asyncio.Future.set_exception) вызван, но исключение никогда не потребляется, [`sys.excepthook()`](https://python-all.ru/3.6/library/sys.html#sys.excepthook) не вызывается. Вместо этого [выдаётся запись в журнале](https://python-all.ru/3.6/library/asyncio-dev.html#asyncio-logger), когда future удаляется сборщиком мусора, с трассировкой, где было вызвано исключение.

Пример необработанного исключения:

```python
import asyncio

@asyncio.coroutine
def bug():
    raise Exception("not consumed")

loop = asyncio.get_event_loop()
asyncio.ensure_future(bug())
loop.run_forever()
loop.close()
```

Вывод:

```python
Task exception was never retrieved
future: <Task finished coro=<coro() done, defined at asyncio/coroutines.py:139> exception=Exception('not consumed',)>
Traceback (most recent call last):
  File "asyncio/tasks.py", line 237, in _step
    result = next(coro)
  File "asyncio/coroutines.py", line 141, in coro
    res = func(*args, **kw)
  File "test.py", line 5, in bug
    raise Exception("not consumed")
Exception: not consumed
```

[Включите отладочный режим asyncio](https://python-all.ru/3.6/library/asyncio-dev.html#asyncio-debug-mode), чтобы получить трассировку, где была создана задача. Вывод в отладочном режиме:

```python
Task exception was never retrieved
future: <Task finished coro=<bug() done, defined at test.py:3> exception=Exception('not consumed',) created at test.py:8>
source_traceback: Object created at (most recent call last):
  File "test.py", line 8, in <module>
    asyncio.ensure_future(bug())
Traceback (most recent call last):
  File "asyncio/tasks.py", line 237, in _step
    result = next(coro)
  File "asyncio/coroutines.py", line 79, in __next__
    return next(self.gen)
  File "asyncio/coroutines.py", line 141, in coro
    res = func(*args, **kw)
  File "test.py", line 5, in bug
    raise Exception("not consumed")
Exception: not consumed
```

Есть несколько способов исправить эту проблему. Первый – включить корутину в другую корутину и использовать классический try/except:

```python
@asyncio.coroutine
def handle_exception():
    try:
        yield from bug()
    except Exception:
        print("exception consumed")

loop = asyncio.get_event_loop()
asyncio.ensure_future(handle_exception())
loop.run_forever()
loop.close()
```

Другой вариант – использовать функцию [`AbstractEventLoop.run_until_complete()`](https://python-all.ru/3.6/library/asyncio-eventloop.html#asyncio.AbstractEventLoop.run_until_complete):

```python
task = asyncio.ensure_future(bug())
try:
    loop.run_until_complete(task)
except Exception:
    print("exception consumed")
```

> **См. также**
>
> Метод [`Future.exception()`](https://python-all.ru/3.6/library/asyncio-task.html#asyncio.Future.exception).

## 18.5.9.8. Правильное связывание корутин

Когда корутинная функция вызывает другие корутинные функции и задачи, их следует явно связывать с помощью `yield from`. В противном случае выполнение не гарантируется последовательным.

Пример с разными ошибками с использованием [`asyncio.sleep()`](https://python-all.ru/3.6/library/asyncio-task.html#asyncio.sleep) для имитации медленных операций:

```python
import asyncio

@asyncio.coroutine
def create():
    yield from asyncio.sleep(3.0)
    print("(1) create file")

@asyncio.coroutine
def write():
    yield from asyncio.sleep(1.0)
    print("(2) write into file")

@asyncio.coroutine
def close():
    print("(3) close file")

@asyncio.coroutine
def test():
    asyncio.ensure_future(create())
    asyncio.ensure_future(write())
    asyncio.ensure_future(close())
    yield from asyncio.sleep(2.0)
    loop.stop()

loop = asyncio.get_event_loop()
asyncio.ensure_future(test())
loop.run_forever()
print("Pending tasks at exit: %s" % asyncio.Task.all_tasks(loop))
loop.close()
```

Ожидаемый вывод:

```text
(1) create file
(2) write into file
(3) close file
Pending tasks at exit: set()
```

Фактический вывод:

```text
(3) close file
(2) write into file
Pending tasks at exit: {<Task pending create() at test.py:7 wait_for=<Future pending cb=[Task._wakeup()]>>}
Task was destroyed but it is pending!
task: <Task pending create() done at test.py:5 wait_for=<Future pending cb=[Task._wakeup()]>>
```

Цикл событий остановился до завершения `create()`, `close()` был вызван до `write()`, хотя корутинные функции вызывались в таком порядке: `create()`, `write()`, `close()`.

Чтобы исправить пример, необходимо пометить задачи с помощью `yield from`:

```python
@asyncio.coroutine
def test():
    yield from asyncio.ensure_future(create())
    yield from asyncio.ensure_future(write())
    yield from asyncio.ensure_future(close())
    yield from asyncio.sleep(2.0)
    loop.stop()
```

Или без `asyncio.ensure_future()`:

```python
@asyncio.coroutine
def test():
    yield from create()
    yield from write()
    yield from close()
    yield from asyncio.sleep(2.0)
    loop.stop()
```

## 18.5.9.9. Ожидающая задача уничтожена

Если ожидающая задача уничтожена, выполнение её обёрнутой [корутины](https://python-all.ru/3.6/library/asyncio-task.html#coroutine) не завершилось. Это, вероятно, ошибка, поэтому выводится предупреждение.

Пример записи в журнале:

```text
Task was destroyed but it is pending!
task: <Task pending coro=<kill_me() done, defined at test.py:5> wait_for=<Future pending cb=[Task._wakeup()]>>
```

[Включите отладочный режим asyncio](https://python-all.ru/3.6/library/asyncio-dev.html#asyncio-debug-mode), чтобы получить трассировку, где была создана задача. Пример записи в журнале в отладочном режиме:

```text
Task was destroyed but it is pending!
source_traceback: Object created at (most recent call last):
  File "test.py", line 15, in <module>
    task = asyncio.ensure_future(coro, loop=loop)
task: <Task pending coro=<kill_me() done, defined at test.py:5> wait_for=<Future pending cb=[Task._wakeup()] created at test.py:7> created at test.py:15>
```

> **См. также**
>
> [Обнаружение объектов корутин, которые никогда не планировались](https://python-all.ru/3.6/library/asyncio-dev.html#asyncio-coroutine-not-scheduled).

## 18.5.9.10. Закрытие транспортов и циклов событий

Когда транспорт больше не нужен, вызовите его метод `close()`, чтобы освободить ресурсы. Циклы событий также необходимо явно закрывать.

Если транспорт или цикл событий не был явно закрыт, в его деструкторе будет выдано предупреждение [`ResourceWarning`](https://python-all.ru/3.6/library/exceptions.html#ResourceWarning). По умолчанию предупреждения [`ResourceWarning`](https://python-all.ru/3.6/library/exceptions.html#ResourceWarning) игнорируются. В разделе [Режим отладки asyncio](https://python-all.ru/3.6/library/asyncio-dev.html#asyncio-debug-mode) объясняется, как их отобразить.
