asyncio-dev.md
1> **Источник:** https://python-all.ru/3.7/library/asyncio-dev.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# Разработка с asyncio89Асинхронное программирование отличается от классического «последовательного» программирования.1011На этой странице перечислены типичные ошибки и ловушки, а также объясняется, как их избежать.1213## Режим отладки1415По умолчанию asyncio работает в производственном режиме. Для упрощения разработки в asyncio есть *режим отладки*.1617Включить режим отладки asyncio можно несколькими способами:1819- Установка переменной окружения [`PYTHONASYNCIODEBUG`](https://python-all.ru/3.7/using/cmdline.html#envvar-PYTHONASYNCIODEBUG) в значение `1`.20- Использование опции командной строки Python [`-X`](https://python-all.ru/3.7/using/cmdline.html#id5) `dev`21- Передача `debug=True` в [`asyncio.run()`](https://python-all.ru/3.7/library/asyncio-task.html#asyncio.run).22- Вызов [`loop.set_debug()`](https://python-all.ru/3.7/library/asyncio-eventloop.html#asyncio.loop.set_debug).2324В дополнение к включению режима отладки также рекомендуется:2526- Установка уровня журналирования [логгера asyncio](https://python-all.ru/3.7/library/asyncio-dev.html#asyncio-logger) на `logging.DEBUG`; например, следующий фрагмент кода можно выполнить при запуске приложения:2728 ```python29 logging.basicConfig(level=logging.DEBUG)30 ```31- Настройка модуля [`warnings`](https://python-all.ru/3.7/library/warnings.html#module-warnings) на отображение предупреждений [`ResourceWarning`](https://python-all.ru/3.7/library/exceptions.html#ResourceWarning). Один из способов сделать это – использовать параметр командной строки [`-W`](https://python-all.ru/3.7/using/cmdline.html#cmdoption-w) `default`.3233Когда режим отладки включён:3435- asyncio проверяет [корутины, которые не были ожиданы](https://python-all.ru/3.7/library/asyncio-dev.html#asyncio-coroutine-not-scheduled) и регистрирует их; это помогает избежать ошибки «забытого await».36- Многие небезопасные по отношению к потокам API asyncio (например, методы [`loop.call_soon()`](https://python-all.ru/3.7/library/asyncio-eventloop.html#asyncio.loop.call_soon) и [`loop.call_at()`](https://python-all.ru/3.7/library/asyncio-eventloop.html#asyncio.loop.call_at)) вызывают исключение, если их вызывают не из того потока.37- Время выполнения селектора ввода-вывода журналируется, если выполнение операции ввода-вывода занимает слишком много времени.38- Колбэки, выполняющиеся дольше 100 мс, записываются в журнал. С помощью атрибута `loop.slow_callback_duration` можно задать минимальную длительность выполнения в секундах, которая считается «медленной».3940## Параллелизм и многопоточность4142Цикл событий работает в потоке (обычно в главном) и выполняет все колбэки и задачи в своём потоке. Пока задача выполняется в цикле событий, никакие другие задачи не могут выполняться в том же потоке. Когда задача выполняет выражение `await`, она приостанавливается, и цикл событий переходит к следующей задаче.4344Чтобы запланировать колбэк из другого потока ОС, следует использовать метод [`loop.call_soon_threadsafe()`](https://python-all.ru/3.7/library/asyncio-eventloop.html#asyncio.loop.call_soon_threadsafe). Пример:4546```python47loop.call_soon_threadsafe(callback, *args)48```4950Почти все объекты asyncio не являются потокобезопасными, что обычно не является проблемой, если только нет кода, который работает с ними вне задачи или колбэка. Если такому коду нужно вызвать низкоуровневый API asyncio, следует использовать метод [`loop.call_soon_threadsafe()`](https://python-all.ru/3.7/library/asyncio-eventloop.html#asyncio.loop.call_soon_threadsafe), например:5152```python53loop.call_soon_threadsafe(fut.cancel)54```5556Для планирования объекта корутины из другого потока ОС следует использовать функцию [`run_coroutine_threadsafe()`](https://python-all.ru/3.7/library/asyncio-task.html#asyncio.run_coroutine_threadsafe). Она возвращает [`concurrent.futures.Future`](https://python-all.ru/3.7/library/concurrent.futures.html#concurrent.futures.Future) для доступа к результату:5758```python59async def coro_func():60 return await asyncio.sleep(1, 42)6162# Позже в другом потоке ОС:6364future = asyncio.run_coroutine_threadsafe(coro_func(), loop)65# Ожидать результат:66result = future.result()67```6869Для обработки сигналов и выполнения подпроцессов цикл событий должен запускаться в главном потоке.7071Метод [`loop.run_in_executor()`](https://python-all.ru/3.7/library/asyncio-eventloop.html#asyncio.loop.run_in_executor) можно использовать с [`concurrent.futures.ThreadPoolExecutor`](https://python-all.ru/3.7/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor) для выполнения блокирующего кода в другом потоке ОС, не блокируя поток ОС, в котором работает цикл событий.7273## Выполнение блокирующего кода7475Блокирующий (связанный с процессором) код не следует вызывать напрямую. Например, если функция выполняет интенсивные вычисления в течение 1 секунды, все параллельные задачи asyncio и операции ввода-вывода будут задержаны на 1 секунду.7677Исполнитель можно использовать для выполнения задачи в другом потоке или даже в другом процессе, чтобы избежать блокировки потока ОС циклом событий. Подробнее см. метод [`loop.run_in_executor()`](https://python-all.ru/3.7/library/asyncio-eventloop.html#asyncio.loop.run_in_executor) для получения дополнительных сведений.7879## Журналирование8081asyncio использует модуль [`logging`](https://python-all.ru/3.7/library/logging.html#module-logging), а все логирование выполняется через логгер `"asyncio"`.8283Уровень логирования по умолчанию – `logging.INFO`. Его можно легко изменить:8485```python86logging.getLogger("asyncio").setLevel(logging.WARNING)87```8889## Обнаружение никогда не ожидаемых корутин9091Когда корутинная функция вызывается, но не ожидается (например, `coro()` вместо `await coro()`) или корутина не запланирована с помощью [`asyncio.create_task()`](https://python-all.ru/3.7/library/asyncio-task.html#asyncio.create_task), asyncio выдаёт сообщение [`RuntimeWarning`](https://python-all.ru/3.7/library/exceptions.html#RuntimeWarning):9293```python94import asyncio9596async def test():97 print("never scheduled")9899async def main():100 test()101102asyncio.run(main())103```104105Вывод:106107```python108test.py:7: RuntimeWarning: coroutine 'test' was never awaited109 test()110```111112Вывод в режиме отладки:113114```python115test.py:7: RuntimeWarning: coroutine 'test' was never awaited116Coroutine created at (most recent call last)117 File "../t.py", line 9, in <module>118 asyncio.run(main(), debug=True)119120 < .. >121122 File "../t.py", line 7, in main123 test()124 test()125```126127Обычно это исправляется либо ожиданием корутины, либо вызовом функции [`asyncio.create_task()`](https://python-all.ru/3.7/library/asyncio-task.html#asyncio.create_task):128129```python130async def main():131 await test()132```133134## Обнаружение никогда не извлечённых исключений135136Если вызван метод [`Future.set_exception()`](https://python-all.ru/3.7/library/asyncio-future.html#asyncio.Future.set_exception), но сам объект Future никогда не ожидается, исключение не будет передано пользовательскому коду. В этом случае asyncio выдаёт сообщение в журнал при сборке мусора для этого Future.137138Пример необработанного исключения:139140```python141import asyncio142143async def bug():144 raise Exception("not consumed")145146async def main():147 asyncio.create_task(bug())148149asyncio.run(main())150```151152Вывод:153154```python155Task exception was never retrieved156future: <Task finished coro=<bug() done, defined at test.py:3>157 exception=Exception('not consumed')>158159Traceback (most recent call last):160 File "test.py", line 4, in bug161 raise Exception("not consumed")162Exception: not consumed163```164165[Включите режим отладки](https://python-all.ru/3.7/library/asyncio-dev.html#asyncio-debug-mode), чтобы получить трассировку стека на момент создания задачи:166167```python168asyncio.run(main(), debug=True)169```170171Вывод в режиме отладки:172173```python174Task exception was never retrieved175future: <Task finished coro=<bug() done, defined at test.py:3>176 exception=Exception('not consumed') created at asyncio/tasks.py:321>177178source_traceback: Object created at (most recent call last):179 File "../t.py", line 9, in <module>180 asyncio.run(main(), debug=True)181182< .. >183184Traceback (most recent call last):185 File "../t.py", line 4, in bug186 raise Exception("not consumed")187Exception: not consumed188```189