Содержание страницы
Разработка с asyncio¶Developing with asyncio
Асинхронное программирование отличается от классического «последовательного» программирования.
На этой странице перечислены типичные ошибки и ловушки, а также объясняется, как их избежать.
Режим отладки¶Debug Mode
По умолчанию asyncio работает в производственном режиме. Для упрощения разработки в asyncio есть режим отладки.
Включить режим отладки asyncio можно несколькими способами:
Установка переменной окружения
PYTHONASYNCIODEBUGв значение1.Использование режима разработки Python.
Передача
debug=Trueвasyncio.run().Вызов
loop.set_debug().
В дополнение к включению режима отладки также рекомендуется:
Установка уровня журналирования логгера asyncio на
logging.DEBUG; например, следующий фрагмент кода можно выполнить при запуске приложения:logging.basicConfig(level=logging.DEBUG)
Настройка модуля
warningsна отображение предупрежденийResourceWarning. Один из способов сделать это – использовать параметр командной строки-Wdefault.
Когда режим отладки включён:
asyncio проверяет корутины, которые не были ожиданы и регистрирует их; это помогает избежать ошибки «забытого await».
Многие небезопасные по отношению к потокам API asyncio (например, методы
loop.call_soon()иloop.call_at()) вызывают исключение, если их вызывают не из того потока.Время выполнения селектора ввода-вывода журналируется, если выполнение операции ввода-вывода занимает слишком много времени.
Колбэки, выполняющиеся дольше 100 миллисекунд, журналируются. Атрибут
loop.slow_callback_durationможно использовать для задания минимальной продолжительности выполнения в секундах, которая считается «медленной».
Параллелизм и многопоточность¶Concurrency and Multithreading
Цикл событий работает в потоке (обычно в главном) и выполняет
все колбэки и задачи в своём потоке. Пока задача выполняется в
цикле событий, никакие другие задачи не могут выполняться в том же потоке. Когда задача
выполняет выражение await, она приостанавливается, и
цикл событий переходит к следующей задаче.
Для планирования колбэка из другого потока ОС следует использовать
метод loop.call_soon_threadsafe(). Пример:
loop.call_soon_threadsafe(callback, *args)
Почти все объекты asyncio не являются потокобезопасными, что обычно
не является проблемой, если только нет кода, который работает с ними вне
задачи или колбэка. Если такому коду нужно вызвать
низкоуровневый API asyncio, следует использовать метод
loop.call_soon_threadsafe(), например:
loop.call_soon_threadsafe(fut.cancel)
Для планирования объекта корутины из другого потока ОС следует использовать
функцию run_coroutine_threadsafe(). Она возвращает
concurrent.futures.Future для доступа к результату:
async def coro_func():
return await asyncio.sleep(1, 42)
# Позже в другом потоке ОС:
future = asyncio.run_coroutine_threadsafe(coro_func(), loop)
# Ожидать результат:
result = future.result()
Для обработки сигналов цикл событий должен выполняться в главном потоке.
Метод loop.run_in_executor() можно использовать с
concurrent.futures.ThreadPoolExecutor для выполнения
блокирующего кода в другом потоке ОС, не блокируя поток ОС,
в котором работает цикл событий.
В настоящее время нет способа напрямую планировать корутины или колбэки из другого процесса (например, запущенного с помощью multiprocessing). Раздел Методы цикла событий перечисляет API, которые могут читать из каналов и отслеживать файловые дескрипторы, не блокируя цикл событий. Кроме того, API подпроцессов asyncio предоставляют способ запустить процесс и взаимодействовать с ним из цикла событий. Наконец, упомянутый метод loop.run_in_executor() также может использоваться с concurrent.futures.ProcessPoolExecutor для выполнения кода в другом процессе.
Выполнение блокирующего кода¶Running Blocking Code
Блокирующий (связанный с процессором) код не следует вызывать напрямую. Например, если функция выполняет интенсивные вычисления в течение 1 секунды, все параллельные задачи asyncio и операции ввода-вывода будут задержаны на 1 секунду.
Исполнитель можно использовать для выполнения задачи в другом потоке или даже в
другом процессе, чтобы избежать блокировки потока ОС
циклом событий. Подробнее см. метод loop.run_in_executor() для получения
дополнительных сведений.
Журналирование¶Logging
asyncio использует модуль logging, а все логирование выполняется
через логгер "asyncio".
Уровень логирования по умолчанию – logging.INFO. Его можно легко
изменить:
logging.getLogger("asyncio").setLevel(logging.WARNING)
Сетевое логирование может блокировать цикл событий. Рекомендуется использовать отдельный поток для обработки логов или неблокирующий ввод-вывод. Пример см. в разделе «Обработчики, которые блокируют выполнение».
Обнаружение никогда не ожидаемых корутин¶Detect never-awaited coroutines
Когда корутинная функция вызывается, но не ожидается
(например, coro() вместо await coro())
или корутина не запланирована с помощью asyncio.create_task(), asyncio
выдаёт сообщение RuntimeWarning:
import asyncio
async def test():
print("never scheduled")
async def main():
test()
asyncio.run(main())
Вывод:
test.py:7: RuntimeWarning: coroutine 'test' was never awaited
test()
Вывод в режиме отладки:
test.py:7: RuntimeWarning: coroutine 'test' was never awaited
Coroutine created at (most recent call last)
File "../t.py", line 9, in <module>
asyncio.run(main(), debug=True)
< .. >
File "../t.py", line 7, in main
test()
test()
Обычно это исправляется либо ожиданием корутины, либо вызовом
функции asyncio.create_task():
async def main():
await test()
Обнаружение никогда не извлечённых исключений¶Detect never-retrieved exceptions
Если вызван метод Future.set_exception(), но сам объект Future никогда
не ожидается, исключение не будет передано пользовательскому коду. В этом
случае asyncio выдаёт сообщение в журнал при сборке мусора для этого Future.
Пример необработанного исключения:
import asyncio
async def bug():
raise Exception("not consumed")
async def main():
asyncio.create_task(bug())
asyncio.run(main())
Вывод:
Task exception was never retrieved
future: <Task finished coro=<bug() done, defined at test.py:3>
exception=Exception('not consumed')>
Traceback (most recent call last):
File "test.py", line 4, in bug
raise Exception("not consumed")
Exception: not consumed
Включите режим отладки, чтобы получить трассировку стека на момент создания задачи:
asyncio.run(main(), debug=True)
Вывод в режиме отладки:
Task exception was never retrieved
future: <Task finished coro=<bug() done, defined at test.py:3>
exception=Exception('not consumed') created at asyncio/tasks.py:321>
source_traceback: Object created at (most recent call last):
File "../t.py", line 9, in <module>
asyncio.run(main(), debug=True)
< .. >
Traceback (most recent call last):
File "../t.py", line 4, in bug
raise Exception("not consumed")
Exception: not consumed