asyncio-dev.md
1> **Источник:** https://python-all.ru/3.6/library/asyncio-dev.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# 18.5.9. Разработка с asyncio89Асинхронное программирование отличается от классического «последовательного» программирования. На этой странице перечислены типичные ловушки и объясняется, как их избежать.1011## 18.5.9.1. Режим отладки asyncio1213Реализация [`asyncio`](https://python-all.ru/3.6/library/asyncio.html#module-asyncio) написана с расчётом на производительность. Чтобы упростить разработку асинхронного кода, можно включить *режим отладки*.1415Чтобы включить все проверки отладки для приложения:1617- Включите глобальный режим отладки 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).18- Установите уровень журналирования [логгера asyncio](https://python-all.ru/3.6/library/asyncio-dev.html#asyncio-logger) в `logging.DEBUG`. Например, вызовите `logging.basicConfig(level=logging.DEBUG)` при запуске.19- Настройте модуль [`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, чтобы показать их.2021Примеры проверок отладки:2223- Журналируйте [корутины, которые определены, но никогда не вызывались через yield from](https://python-all.ru/3.6/library/asyncio-dev.html#asyncio-coroutine-not-scheduled)24- Методы [`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) вызывают исключение, если их вызвать из неправильного потока.25- Журналируйте время выполнения селектора26- Журналируйте колбэки, выполнение которых занимает более 100 мс. Атрибут `AbstractEventLoop.slow_callback_duration` задаёт минимальную продолжительность в секундах для «медленных» колбэков.27- Предупреждения [`ResourceWarning`](https://python-all.ru/3.6/library/exceptions.html#ResourceWarning) выводятся, когда транспорты и циклы событий [не закрыты явно](https://python-all.ru/3.6/library/asyncio-dev.html#asyncio-close-transports).2829> **См. также**30>31> Метод [`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).3233## 18.5.9.2. Отмена3435Отмена задач не является распространённой в классическом программировании. В асинхронном программировании это не только обычное явление, но и необходимо подготовить свой код к её обработке.3637Future и задачи могут быть отменены явно с помощью их метода [`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) отменяет ожидаемую задачу при наступлении тайм-аута. Существует много других случаев, когда задача может быть отменена косвенно.3839Не вызывайте метод [`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 отменён: это приведёт к исключению. Например, напишите:4041```python42if not fut.cancelled():43 fut.set_result('done')44```4546Не планируйте напрямую вызов метода [`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 может быть отменён до того, как его метод будет вызван.4748Если вы ожидаете future, следует заранее проверить, не был ли future отменён, чтобы избежать бесполезных операций. Пример:4950```python51@coroutine52def slow_operation(fut):53 if fut.cancelled():54 return55 # ... медленное вычисление ...56 yield from fut57 # ...58```5960Функция [`shield()`](https://python-all.ru/3.6/library/asyncio-task.html#asyncio.shield) также может использоваться для игнорирования отмены.6162## 18.5.9.3. Конкурентность и многопоточность6364Цикл событий работает в одном потоке и выполняет все колбэки и задачи в том же потоке. Пока задача выполняется в цикле событий, никакая другая задача не выполняется в том же потоке. Но когда задача использует `yield from`, она приостанавливается, и цикл событий выполняет следующую задачу.6566Чтобы запланировать колбэк из другого потока, следует использовать метод [`AbstractEventLoop.call_soon_threadsafe()`](https://python-all.ru/3.6/library/asyncio-eventloop.html#asyncio.AbstractEventLoop.call_soon_threadsafe). Пример:6768```python69loop.call_soon_threadsafe(callback, *args)70```7172Большинство объектов asyncio не являются потокобезопасными. Стоит беспокоиться только при доступе к объектам за пределами цикла событий. Например, чтобы отменить future, не вызывайте напрямую его метод [`Future.cancel()`](https://python-all.ru/3.6/library/asyncio-task.html#asyncio.Future.cancel), а:7374```python75loop.call_soon_threadsafe(fut.cancel)76```7778Для обработки сигналов и выполнения подпроцессов цикл событий должен выполняться в главном потоке.7980Чтобы запланировать объект корутины из другого потока, следует использовать функцию [`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) для доступа к результату:8182```python83future = asyncio.run_coroutine_threadsafe(coro_func(), loop)84result = future.result(timeout) # Ожидание результата с тайм-аутом85```8687Метод [`AbstractEventLoop.run_in_executor()`](https://python-all.ru/3.6/library/asyncio-eventloop.html#asyncio.AbstractEventLoop.run_in_executor) можно использовать с исполнителем пула потоков, чтобы выполнить колбэк в другом потоке и не блокировать поток цикла событий.8889> **См. также**90>91> Раздел [Примитивы синхронизации](https://python-all.ru/3.6/library/asyncio-sync.html#asyncio-sync) описывает способы синхронизации задач.92>93> Раздел [Подпроцессы и потоки](https://python-all.ru/3.6/library/asyncio-subprocess.html#asyncio-subprocess-threads) перечисляет ограничения asyncio для запуска подпроцессов из разных потоков.9495## 18.5.9.4. Правильная обработка блокирующих функций9697Блокирующие функции нельзя вызывать напрямую. Например, если функция блокируется на 1 секунду, другие задачи задерживаются на 1 секунду, что может существенно повлиять на отзывчивость.9899Для работы с сетью и подпроцессами модуль [`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).100101Исполнитель можно использовать для запуска задачи в другом потоке или даже в другом процессе, чтобы не блокировать поток цикла событий. См. метод [`AbstractEventLoop.run_in_executor()`](https://python-all.ru/3.6/library/asyncio-eventloop.html#asyncio.AbstractEventLoop.run_in_executor).102103> **См. также**104>105> Раздел [Отложенные вызовы](https://python-all.ru/3.6/library/asyncio-eventloop.html#asyncio-delayed-calls) подробно описывает, как цикл событий обрабатывает время.106107## 18.5.9.5. Логирование108109Модуль [`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'`.110111Уровень логирования по умолчанию для модуля [`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`:112113```text114logging.getLogger('asyncio').setLevel(logging.WARNING)115```116117## 18.5.9.6. Обнаружение объектов корутин, которые никогда не планировались118119Когда вызывается корутинная функция и её результат не передаётся в [`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) для его обнаружения.120121Пример с ошибкой:122123```python124import asyncio125126@asyncio.coroutine127def test():128 print("never scheduled")129130test()131```132133Вывод в режиме отладки:134135```python136Coroutine test() at test.py:3 was never yielded from137Coroutine object created at (most recent call last):138 File "test.py", line 7, in <module>139 test()140```141142Исправление заключается в вызове функции [`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) с объектом корутины.143144> **См. также**145>146> [Уничтожение ожидающей задачи](https://python-all.ru/3.6/library/asyncio-dev.html#asyncio-pending-task-destroyed).147148## 18.5.9.7. Обнаружение непоглощённых исключений149150Python обычно вызывает [`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 удаляется сборщиком мусора, с трассировкой, где было вызвано исключение.151152Пример необработанного исключения:153154```python155import asyncio156157@asyncio.coroutine158def bug():159 raise Exception("not consumed")160161loop = asyncio.get_event_loop()162asyncio.ensure_future(bug())163loop.run_forever()164loop.close()165```166167Вывод:168169```python170Task exception was never retrieved171future: <Task finished coro=<coro() done, defined at asyncio/coroutines.py:139> exception=Exception('not consumed',)>172Traceback (most recent call last):173 File "asyncio/tasks.py", line 237, in _step174 result = next(coro)175 File "asyncio/coroutines.py", line 141, in coro176 res = func(*args, **kw)177 File "test.py", line 5, in bug178 raise Exception("not consumed")179Exception: not consumed180```181182[Включите отладочный режим asyncio](https://python-all.ru/3.6/library/asyncio-dev.html#asyncio-debug-mode), чтобы получить трассировку, где была создана задача. Вывод в отладочном режиме:183184```python185Task exception was never retrieved186future: <Task finished coro=<bug() done, defined at test.py:3> exception=Exception('not consumed',) created at test.py:8>187source_traceback: Object created at (most recent call last):188 File "test.py", line 8, in <module>189 asyncio.ensure_future(bug())190Traceback (most recent call last):191 File "asyncio/tasks.py", line 237, in _step192 result = next(coro)193 File "asyncio/coroutines.py", line 79, in __next__194 return next(self.gen)195 File "asyncio/coroutines.py", line 141, in coro196 res = func(*args, **kw)197 File "test.py", line 5, in bug198 raise Exception("not consumed")199Exception: not consumed200```201202Есть несколько способов исправить эту проблему. Первый – включить корутину в другую корутину и использовать классический try/except:203204```python205@asyncio.coroutine206def handle_exception():207 try:208 yield from bug()209 except Exception:210 print("exception consumed")211212loop = asyncio.get_event_loop()213asyncio.ensure_future(handle_exception())214loop.run_forever()215loop.close()216```217218Другой вариант – использовать функцию [`AbstractEventLoop.run_until_complete()`](https://python-all.ru/3.6/library/asyncio-eventloop.html#asyncio.AbstractEventLoop.run_until_complete):219220```python221task = asyncio.ensure_future(bug())222try:223 loop.run_until_complete(task)224except Exception:225 print("exception consumed")226```227228> **См. также**229>230> Метод [`Future.exception()`](https://python-all.ru/3.6/library/asyncio-task.html#asyncio.Future.exception).231232## 18.5.9.8. Правильное связывание корутин233234Когда корутинная функция вызывает другие корутинные функции и задачи, их следует явно связывать с помощью `yield from`. В противном случае выполнение не гарантируется последовательным.235236Пример с разными ошибками с использованием [`asyncio.sleep()`](https://python-all.ru/3.6/library/asyncio-task.html#asyncio.sleep) для имитации медленных операций:237238```python239import asyncio240241@asyncio.coroutine242def create():243 yield from asyncio.sleep(3.0)244 print("(1) create file")245246@asyncio.coroutine247def write():248 yield from asyncio.sleep(1.0)249 print("(2) write into file")250251@asyncio.coroutine252def close():253 print("(3) close file")254255@asyncio.coroutine256def test():257 asyncio.ensure_future(create())258 asyncio.ensure_future(write())259 asyncio.ensure_future(close())260 yield from asyncio.sleep(2.0)261 loop.stop()262263loop = asyncio.get_event_loop()264asyncio.ensure_future(test())265loop.run_forever()266print("Pending tasks at exit: %s" % asyncio.Task.all_tasks(loop))267loop.close()268```269270Ожидаемый вывод:271272```text273(1) create file274(2) write into file275(3) close file276Pending tasks at exit: set()277```278279Фактический вывод:280281```text282(3) close file283(2) write into file284Pending tasks at exit: {<Task pending create() at test.py:7 wait_for=<Future pending cb=[Task._wakeup()]>>}285Task was destroyed but it is pending!286task: <Task pending create() done at test.py:5 wait_for=<Future pending cb=[Task._wakeup()]>>287```288289Цикл событий остановился до завершения `create()`, `close()` был вызван до `write()`, хотя корутинные функции вызывались в таком порядке: `create()`, `write()`, `close()`.290291Чтобы исправить пример, необходимо пометить задачи с помощью `yield from`:292293```python294@asyncio.coroutine295def test():296 yield from asyncio.ensure_future(create())297 yield from asyncio.ensure_future(write())298 yield from asyncio.ensure_future(close())299 yield from asyncio.sleep(2.0)300 loop.stop()301```302303Или без `asyncio.ensure_future()`:304305```python306@asyncio.coroutine307def test():308 yield from create()309 yield from write()310 yield from close()311 yield from asyncio.sleep(2.0)312 loop.stop()313```314315## 18.5.9.9. Ожидающая задача уничтожена316317Если ожидающая задача уничтожена, выполнение её обёрнутой [корутины](https://python-all.ru/3.6/library/asyncio-task.html#coroutine) не завершилось. Это, вероятно, ошибка, поэтому выводится предупреждение.318319Пример записи в журнале:320321```text322Task was destroyed but it is pending!323task: <Task pending coro=<kill_me() done, defined at test.py:5> wait_for=<Future pending cb=[Task._wakeup()]>>324```325326[Включите отладочный режим asyncio](https://python-all.ru/3.6/library/asyncio-dev.html#asyncio-debug-mode), чтобы получить трассировку, где была создана задача. Пример записи в журнале в отладочном режиме:327328```text329Task was destroyed but it is pending!330source_traceback: Object created at (most recent call last):331 File "test.py", line 15, in <module>332 task = asyncio.ensure_future(coro, loop=loop)333task: <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>334```335336> **См. также**337>338> [Обнаружение объектов корутин, которые никогда не планировались](https://python-all.ru/3.6/library/asyncio-dev.html#asyncio-coroutine-not-scheduled).339340## 18.5.9.10. Закрытие транспортов и циклов событий341342Когда транспорт больше не нужен, вызовите его метод `close()`, чтобы освободить ресурсы. Циклы событий также необходимо явно закрывать.343344Если транспорт или цикл событий не был явно закрыт, в его деструкторе будет выдано предупреждение [`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) объясняется, как их отобразить.345