asyncio-dev.md
1> **Источник:** https://python-all.ru/3.12/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.12/using/cmdline.html#envvar-PYTHONASYNCIODEBUG) в значение `1`.20- Использование [режима разработки Python](https://python-all.ru/3.12/library/devmode.html#devmode).21- Передача `debug=True` в [`asyncio.run()`](https://python-all.ru/3.12/library/asyncio-runner.html#asyncio.run).22- Вызов [`loop.set_debug()`](https://python-all.ru/3.12/library/asyncio-eventloop.html#asyncio.loop.set_debug).2324В дополнение к включению режима отладки также рекомендуется:2526- Установка уровня журналирования [логгера asyncio](https://python-all.ru/3.12/library/asyncio-dev.html#asyncio-logger) на [`logging.DEBUG`](https://python-all.ru/3.12/library/logging.html#logging.DEBUG); например, следующий фрагмент кода можно выполнить при запуске приложения:2728 ```python29 logging.basicConfig(level=logging.DEBUG)30 ```31- Настройка модуля [`warnings`](https://python-all.ru/3.12/library/warnings.html#module-warnings) на отображение предупреждений [`ResourceWarning`](https://python-all.ru/3.12/library/exceptions.html#ResourceWarning). Один из способов сделать это – использовать параметр командной строки [`-W`](https://python-all.ru/3.12/using/cmdline.html#cmdoption-W) `default`.3233Когда режим отладки включён:3435- asyncio проверяет [корутины, которые не были ожиданы](https://python-all.ru/3.12/library/asyncio-dev.html#asyncio-coroutine-not-scheduled) и регистрирует их; это помогает избежать ошибки «забытого await».36- Многие небезопасные по отношению к потокам API asyncio (например, методы [`loop.call_soon()`](https://python-all.ru/3.12/library/asyncio-eventloop.html#asyncio.loop.call_soon) и [`loop.call_at()`](https://python-all.ru/3.12/library/asyncio-eventloop.html#asyncio.loop.call_at)) вызывают исключение, если их вызывают не из того потока.37- Время выполнения селектора ввода-вывода журналируется, если выполнение операции ввода-вывода занимает слишком много времени.38- Колбэки, выполняющиеся дольше 100 миллисекунд, журналируются. Атрибут [`loop.slow_callback_duration`](https://python-all.ru/3.12/library/asyncio-eventloop.html#asyncio.loop.slow_callback_duration) можно использовать для задания минимальной продолжительности выполнения в секундах, которая считается «медленной».3940## Параллелизм и многопоточность4142Цикл событий работает в потоке (обычно в главном) и выполняет все колбэки и задачи в своём потоке. Пока задача выполняется в цикле событий, никакие другие задачи не могут выполняться в том же потоке. Когда задача выполняет выражение `await`, она приостанавливается, и цикл событий переходит к следующей задаче.4344Для планирования [колбэка](https://python-all.ru/3.12/glossary.html#term-callback) из другого потока ОС следует использовать метод [`loop.call_soon_threadsafe()`](https://python-all.ru/3.12/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.12/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.12/library/asyncio-task.html#asyncio.run_coroutine_threadsafe). Она возвращает [`concurrent.futures.Future`](https://python-all.ru/3.12/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.12/library/asyncio-eventloop.html#asyncio.loop.run_in_executor) можно использовать с [`concurrent.futures.ThreadPoolExecutor`](https://python-all.ru/3.12/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor) для выполнения блокирующего кода в другом потоке ОС, не блокируя поток ОС, в котором работает цикл событий.7273В настоящее время нет способа напрямую планировать корутины или колбэки из другого процесса (например, запущенного с помощью [`multiprocessing`](https://python-all.ru/3.12/library/multiprocessing.html#module-multiprocessing)). Раздел [Методы цикла событий](https://python-all.ru/3.12/library/asyncio-eventloop.html#asyncio-event-loop-methods) перечисляет API, которые могут читать из каналов и отслеживать файловые дескрипторы, не блокируя цикл событий. Кроме того, API [подпроцессов](https://python-all.ru/3.12/library/asyncio-subprocess.html#asyncio-subprocess) asyncio предоставляют способ запустить процесс и взаимодействовать с ним из цикла событий. Наконец, упомянутый метод [`loop.run_in_executor()`](https://python-all.ru/3.12/library/asyncio-eventloop.html#asyncio.loop.run_in_executor) также может использоваться с [`concurrent.futures.ProcessPoolExecutor`](https://python-all.ru/3.12/library/concurrent.futures.html#concurrent.futures.ProcessPoolExecutor) для выполнения кода в другом процессе.7475## Выполнение блокирующего кода7677Блокирующий (связанный с процессором) код не следует вызывать напрямую. Например, если функция выполняет интенсивные вычисления в течение 1 секунды, все параллельные задачи asyncio и операции ввода-вывода будут задержаны на 1 секунду.7879Исполнитель можно использовать для выполнения задачи в другом потоке или даже в другом процессе, чтобы избежать блокировки потока ОС циклом событий. Подробнее см. метод [`loop.run_in_executor()`](https://python-all.ru/3.12/library/asyncio-eventloop.html#asyncio.loop.run_in_executor) для получения дополнительных сведений.8081## Журналирование8283asyncio использует модуль [`logging`](https://python-all.ru/3.12/library/logging.html#module-logging), а все логирование выполняется через логгер `"asyncio"`.8485Уровень логирования по умолчанию – [`logging.INFO`](https://python-all.ru/3.12/library/logging.html#logging.INFO). Его можно легко изменить:8687```python88logging.getLogger("asyncio").setLevel(logging.WARNING)89```9091Сетевое логирование может блокировать цикл событий. Рекомендуется использовать отдельный поток для обработки логов или неблокирующий ввод-вывод. Пример см. в [разделе «Обработчики, которые блокируют выполнение»](https://python-all.ru/3.12/howto/logging-cookbook.html#blocking-handlers).9293## Обнаружение никогда не ожидаемых корутин9495Когда корутинная функция вызывается, но не ожидается (например, `coro()` вместо `await coro()`) или корутина не запланирована с помощью [`asyncio.create_task()`](https://python-all.ru/3.12/library/asyncio-task.html#asyncio.create_task), asyncio выдаёт сообщение [`RuntimeWarning`](https://python-all.ru/3.12/library/exceptions.html#RuntimeWarning):9697```python98import asyncio99100async def test():101 print("never scheduled")102103async def main():104 test()105106asyncio.run(main())107```108109Вывод:110111```python112test.py:7: RuntimeWarning: coroutine 'test' was never awaited113 test()114```115116Вывод в режиме отладки:117118```python119test.py:7: RuntimeWarning: coroutine 'test' was never awaited120Coroutine created at (most recent call last)121 File "../t.py", line 9, in <module>122 asyncio.run(main(), debug=True)123124 < .. >125126 File "../t.py", line 7, in main127 test()128 test()129```130131Обычно это исправляется либо ожиданием корутины, либо вызовом функции [`asyncio.create_task()`](https://python-all.ru/3.12/library/asyncio-task.html#asyncio.create_task):132133```python134async def main():135 await test()136```137138## Обнаружение никогда не извлечённых исключений139140Если вызван метод [`Future.set_exception()`](https://python-all.ru/3.12/library/asyncio-future.html#asyncio.Future.set_exception), но сам объект Future никогда не ожидается, исключение не будет передано пользовательскому коду. В этом случае asyncio выдаёт сообщение в журнал при сборке мусора для этого Future.141142Пример необработанного исключения:143144```python145import asyncio146147async def bug():148 raise Exception("not consumed")149150async def main():151 asyncio.create_task(bug())152153asyncio.run(main())154```155156Вывод:157158```python159Task exception was never retrieved160future: <Task finished coro=<bug() done, defined at test.py:3>161 exception=Exception('not consumed')>162163Traceback (most recent call last):164 File "test.py", line 4, in bug165 raise Exception("not consumed")166Exception: not consumed167```168169[Включите режим отладки](https://python-all.ru/3.12/library/asyncio-dev.html#asyncio-debug-mode), чтобы получить трассировку стека на момент создания задачи:170171```python172asyncio.run(main(), debug=True)173```174175Вывод в режиме отладки:176177```python178Task exception was never retrieved179future: <Task finished coro=<bug() done, defined at test.py:3>180 exception=Exception('not consumed') created at asyncio/tasks.py:321>181182source_traceback: Object created at (most recent call last):183 File "../t.py", line 9, in <module>184 asyncio.run(main(), debug=True)185186< .. >187188Traceback (most recent call last):189 File "../t.py", line 4, in bug190 raise Exception("not consumed")191Exception: not consumed192```193