Документация Python неофициальный перевод

asyncio-dev.md

339 строк · 21.3 КБ · обычная страница · сырой текст · скачать

1> **Источник:** https://python-all.ru/3.4/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.4/library/asyncio.html#module-asyncio) написана для производительности. Чтобы упростить разработку асинхронного кода, может быть полезно включить *режим отладки*.1415Чтобы включить все проверки отладки для приложения:1617- Включите глобальный режим отладки asyncio, установив переменную окружения [`PYTHONASYNCIODEBUG`](https://python-all.ru/3.4/using/cmdline.html#envvar-PYTHONASYNCIODEBUG) в `1` или вызвав [`BaseEventLoop.set_debug()`](https://python-all.ru/3.4/library/asyncio-eventloop.html#asyncio.BaseEventLoop.set_debug).18- Установите уровень журналирования [*логгера asyncio*](https://python-all.ru/3.4/library/asyncio-dev.html#asyncio-logger) на `logging.DEBUG`. Например, вызовите `logging.basicConfig(level=logging.DEBUG)` при запуске.19- Настройте модуль [`warnings`](https://python-all.ru/3.4/library/warnings.html#module-warnings) для отображения предупреждений [`ResourceWarning`](https://python-all.ru/3.4/library/exceptions.html#ResourceWarning). Например, используйте параметр командной строки `-Wdefault` Python для их отображения.2021Примеры проверок отладки:2223- Журналируйте [*корутины, которые определены, но никогда не вызывались через yield from*](https://python-all.ru/3.4/library/asyncio-dev.html#asyncio-coroutine-not-scheduled)24- Методы [`call_soon()`](https://python-all.ru/3.4/library/asyncio-eventloop.html#asyncio.BaseEventLoop.call_soon) и [`call_at()`](https://python-all.ru/3.4/library/asyncio-eventloop.html#asyncio.BaseEventLoop.call_at) вызывают исключение, если они вызваны из неправильного потока.25- Журналируйте время выполнения селектора26- Журналировать колбэки, выполнение которых занимает более 100 мс. Атрибут `BaseEventLoop.slow_callback_duration` задаёт минимальную длительность в секундах для «медленных» колбэков.27- Предупреждения [`ResourceWarning`](https://python-all.ru/3.4/library/exceptions.html#ResourceWarning) выводятся, когда транспорты и циклы событий [*не закрыты явно*](https://python-all.ru/3.4/library/asyncio-dev.html#asyncio-close-transports).2829> **См. также**30>31> Метод [`BaseEventLoop.set_debug()`](https://python-all.ru/3.4/library/asyncio-eventloop.html#asyncio.BaseEventLoop.set_debug) и [*логгер asyncio*](https://python-all.ru/3.4/library/asyncio-dev.html#asyncio-logger).3233## 18.5.9.2. Отмена3435Отмена задач нечасто встречается в классическом программировании. В асинхронном программировании это не только обычное явление, но и требует подготовки кода к её обработке.3637Фьючерсы и задачи можно отменить явно с помощью метода [`Future.cancel()`](https://python-all.ru/3.4/library/asyncio-task.html#asyncio.Future.cancel). Функция [`wait_for()`](https://python-all.ru/3.4/library/asyncio-task.html#asyncio.wait_for) отменяет ожидаемую задачу по истечении тайм-аута. Существует много других случаев, когда задача может быть отменена косвенно.3839Не вызывайте методы [`set_result()`](https://python-all.ru/3.4/library/asyncio-task.html#asyncio.Future.set_result) или [`set_exception()`](https://python-all.ru/3.4/library/asyncio-task.html#asyncio.Future.set_exception) объекта [`Future`](https://python-all.ru/3.4/library/asyncio-task.html#asyncio.Future), если фьючерс отменён: это приведёт к исключению. Например, напишите:4041```python42if not fut.cancelled():43    fut.set_result('done')44```4546Не планируйте напрямую вызов метода [`set_result()`](https://python-all.ru/3.4/library/asyncio-task.html#asyncio.Future.set_result) или [`set_exception()`](https://python-all.ru/3.4/library/asyncio-task.html#asyncio.Future.set_exception) фьючерса через [`BaseEventLoop.call_soon()`](https://python-all.ru/3.4/library/asyncio-eventloop.html#asyncio.BaseEventLoop.call_soon): фьючерс может быть отменён до вызова его метода.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.4/library/asyncio-task.html#asyncio.shield) также можно использовать для игнорирования отмены.6162## 18.5.9.3. Конкурентность и многопоточность6364Цикл событий работает в потоке и выполняет все колбэки и задачи в том же потоке. Пока задача выполняется в цикле событий, никакая другая задача не выполняется в том же потоке. Но когда задача использует `yield from`, она приостанавливается, и цикл событий выполняет следующую задачу.6566Чтобы запланировать колбэк из другого потока, следует использовать метод [`BaseEventLoop.call_soon_threadsafe()`](https://python-all.ru/3.4/library/asyncio-eventloop.html#asyncio.BaseEventLoop.call_soon_threadsafe). Пример:6768```python69loop.call_soon_threadsafe(callback, *args)70```7172Большинство объектов asyncio не являются потокобезопасными. Беспокоиться стоит только при доступе к объектам за пределами цикла событий. Например, чтобы отменить фьючерс, не вызывайте напрямую метод [`Future.cancel()`](https://python-all.ru/3.4/library/asyncio-task.html#asyncio.Future.cancel), а:7374```python75loop.call_soon_threadsafe(fut.cancel)76```7778Чтобы запланировать корутину из другого потока, следует использовать функцию [`run_coroutine_threadsafe()`](https://python-all.ru/3.4/library/asyncio-task.html#asyncio.run_coroutine_threadsafe). Она возвращает объект [`concurrent.futures.Future`](https://python-all.ru/3.4/library/concurrent.futures.html#concurrent.futures.Future) для доступа к результату:7980```python81future = asyncio.run_coroutine_threadsafe(coro_func(), loop)82result = future.result(timeout)  # Ожидание результата с тайм-аутом83```8485Для обработки сигналов и выполнения подпроцессов цикл событий должен выполняться в главном потоке.8687Метод [`BaseEventLoop.run_in_executor()`](https://python-all.ru/3.4/library/asyncio-eventloop.html#asyncio.BaseEventLoop.run_in_executor) можно использовать с пулом потоков исполнителя для выполнения колбэка в другом потоке, чтобы не блокировать поток цикла событий.8889> **См. также**90>91> Раздел [*Примитивы синхронизации*](https://python-all.ru/3.4/library/asyncio-sync.html#asyncio-sync) описывает способы синхронизации задач.92>93> Раздел [*Подпроцессы и потоки*](https://python-all.ru/3.4/library/asyncio-subprocess.html#asyncio-subprocess-threads) перечисляет ограничения asyncio для запуска подпроцессов из разных потоков.9495## 18.5.9.4. Корректная обработка блокирующих функций9697Блокирующие функции нельзя вызывать напрямую. Например, если функция блокируется на 1 секунду, другие задачи задерживаются на 1 секунду, что может существенно повлиять на отзывчивость.9899Для работы с сетью и подпроцессами модуль [`asyncio`](https://python-all.ru/3.4/library/asyncio.html#module-asyncio) предоставляет высокоуровневые API, такие как [*протоколы*](https://python-all.ru/3.4/library/asyncio-protocol.html#asyncio-protocol).100101Исполнитель можно использовать для выполнения задачи в другом потоке или даже в другом процессе, чтобы не блокировать поток цикла событий. См. метод [`BaseEventLoop.run_in_executor()`](https://python-all.ru/3.4/library/asyncio-eventloop.html#asyncio.BaseEventLoop.run_in_executor).102103> **См. также**104>105> Раздел [*Отложенные вызовы*](https://python-all.ru/3.4/library/asyncio-eventloop.html#asyncio-delayed-calls) подробно описывает, как цикл событий обрабатывает время.106107## 18.5.9.5. Журналирование108109Модуль [`asyncio`](https://python-all.ru/3.4/library/asyncio.html#module-asyncio) журналирует информацию с помощью модуля [`logging`](https://python-all.ru/3.4/library/logging.html#module-logging) в логгере `'asyncio'`.110111## 18.5.9.6. Обнаружение корутинных объектов, которые никогда не были запланированы112113Когда вызывается корутинная функция, а её результат не передаётся в [`async()`](https://python-all.ru/3.4/library/asyncio-task.html#asyncio.async) или метод [`BaseEventLoop.create_task()`](https://python-all.ru/3.4/library/asyncio-eventloop.html#asyncio.BaseEventLoop.create_task), выполнение объекта корутины никогда не будет запланировано, что, вероятно, является ошибкой. [*Включите режим отладки asyncio*](https://python-all.ru/3.4/library/asyncio-dev.html#asyncio-debug-mode), чтобы [*зарегистрировать предупреждение*](https://python-all.ru/3.4/library/asyncio-dev.html#asyncio-logger) для обнаружения этого.114115Пример с ошибкой:116117```python118import asyncio119120@asyncio.coroutine121def test():122    print("never scheduled")123124test()125```126127Вывод в режиме отладки:128129```python130Coroutine test() at test.py:3 was never yielded from131Coroutine object created at (most recent call last):132  File "test.py", line 7, in <module>133    test()134```135136Исправление – вызвать функцию [`async()`](https://python-all.ru/3.4/library/asyncio-task.html#asyncio.async) или метод [`BaseEventLoop.create_task()`](https://python-all.ru/3.4/library/asyncio-eventloop.html#asyncio.BaseEventLoop.create_task) с объектом корутины.137138> **См. также**139>140> [*Уничтожение ожидающей задачи*](https://python-all.ru/3.4/library/asyncio-dev.html#asyncio-pending-task-destroyed).141142## 18.5.9.7. Обнаружение исключений, которые никогда не были обработаны143144Python обычно вызывает [`sys.displayhook()`](https://python-all.ru/3.4/library/sys.html#sys.displayhook) для необработанных исключений. Если [`Future.set_exception()`](https://python-all.ru/3.4/library/asyncio-task.html#asyncio.Future.set_exception) вызывается, но исключение никогда не обрабатывается, [`sys.displayhook()`](https://python-all.ru/3.4/library/sys.html#sys.displayhook) не вызывается. Вместо этого [*выводится запись в журнал*](https://python-all.ru/3.4/library/asyncio-dev.html#asyncio-logger), когда фьючерс удаляется сборщиком мусора, с трассировкой места возникновения исключения.145146Пример необработанного исключения:147148```python149import asyncio150151@asyncio.coroutine152def bug():153    raise Exception("not consumed")154155loop = asyncio.get_event_loop()156asyncio.ensure_future(bug())157loop.run_forever()158loop.close()159```160161Вывод:162163```python164Task exception was never retrieved165future: <Task finished coro=<coro() done, defined at asyncio/coroutines.py:139> exception=Exception('not consumed',)>166Traceback (most recent call last):167  File "asyncio/tasks.py", line 237, in _step168    result = next(coro)169  File "asyncio/coroutines.py", line 141, in coro170    res = func(*args, **kw)171  File "test.py", line 5, in bug172    raise Exception("not consumed")173Exception: not consumed174```175176[*Включите отладочный режим asyncio*](https://python-all.ru/3.4/library/asyncio-dev.html#asyncio-debug-mode), чтобы получить трассировку, где была создана задача. Вывод в отладочном режиме:177178```python179Task exception was never retrieved180future: <Task finished coro=<bug() done, defined at test.py:3> exception=Exception('not consumed',) created at test.py:8>181source_traceback: Object created at (most recent call last):182  File "test.py", line 8, in <module>183    asyncio.ensure_future(bug())184Traceback (most recent call last):185  File "asyncio/tasks.py", line 237, in _step186    result = next(coro)187  File "asyncio/coroutines.py", line 79, in __next__188    return next(self.gen)189  File "asyncio/coroutines.py", line 141, in coro190    res = func(*args, **kw)191  File "test.py", line 5, in bug192    raise Exception("not consumed")193Exception: not consumed194```195196Есть несколько способов исправить эту проблему. Первый – включить корутину в другую корутину и использовать классический try/except:197198```python199@asyncio.coroutine200def handle_exception():201    try:202        yield from bug()203    except Exception:204        print("exception consumed")205206loop = asyncio.get_event_loop()207asyncio.ensure_future(handle_exception())208loop.run_forever()209loop.close()210```211212Другой вариант – использовать функцию [`BaseEventLoop.run_until_complete()`](https://python-all.ru/3.4/library/asyncio-eventloop.html#asyncio.BaseEventLoop.run_until_complete) :213214```python215task = asyncio.ensure_future(bug())216try:217    loop.run_until_complete(task)218except Exception:219    print("exception consumed")220```221222> **См. также**223>224> Метод [`Future.exception()`](https://python-all.ru/3.4/library/asyncio-task.html#asyncio.Future.exception).225226## 18.5.9.8. Правильное построение цепочек корутин227228Когда корутинная функция вызывает другие корутинные функции и задачи, их следует явно связывать с помощью `yield from`. В противном случае последовательность выполнения не гарантируется.229230Пример с различными ошибками с использованием [`asyncio.sleep()`](https://python-all.ru/3.4/library/asyncio-task.html#asyncio.sleep) для имитации медленных операций:231232```python233import asyncio234235@asyncio.coroutine236def create():237    yield from asyncio.sleep(3.0)238    print("(1) create file")239240@asyncio.coroutine241def write():242    yield from asyncio.sleep(1.0)243    print("(2) write into file")244245@asyncio.coroutine246def close():247    print("(3) close file")248249@asyncio.coroutine250def test():251    asyncio.ensure_future(create())252    asyncio.ensure_future(write())253    asyncio.ensure_future(close())254    yield from asyncio.sleep(2.0)255    loop.stop()256257loop = asyncio.get_event_loop()258asyncio.ensure_future(test())259loop.run_forever()260print("Pending tasks at exit: %s" % asyncio.Task.all_tasks(loop))261loop.close()262```263264Ожидаемый вывод:265266```python267(1) create file268(2) write into file269(3) close file270Pending tasks at exit: set()271```272273Фактический вывод:274275```python276(3) close file277(2) write into file278Pending tasks at exit: {<Task pending create() at test.py:7 wait_for=<Future pending cb=[Task._wakeup()]>>}279Task was destroyed but it is pending!280task: <Task pending create() done at test.py:5 wait_for=<Future pending cb=[Task._wakeup()]>>281```282283Цикл остановился до завершения `create()`, `close()` был вызван до `write()`, хотя корутинные функции вызывались в таком порядке: `create()`, `write()`, `close()`.284285Чтобы исправить пример, задачи должны быть помечены `yield from`:286287```python288@asyncio.coroutine289def test():290    yield from asyncio.ensure_future(create())291    yield from asyncio.ensure_future(write())292    yield from asyncio.ensure_future(close())293    yield from asyncio.sleep(2.0)294    loop.stop()295```296297Или без `asyncio.ensure_future()`:298299```python300@asyncio.coroutine301def test():302    yield from create()303    yield from write()304    yield from close()305    yield from asyncio.sleep(2.0)306    loop.stop()307```308309## 18.5.9.9. Уничтожение ожидающей задачи310311Если ожидающая задача уничтожена, выполнение её обёрнутой [*корутины*](https://python-all.ru/3.4/library/asyncio-task.html#coroutine) не завершилось. Это, вероятно, ошибка, поэтому выводится предупреждение.312313Пример записи в журнале:314315```python316Task was destroyed but it is pending!317task: <Task pending coro=<kill_me() done, defined at test.py:5> wait_for=<Future pending cb=[Task._wakeup()]>>318```319320[*Включите отладочный режим asyncio*](https://python-all.ru/3.4/library/asyncio-dev.html#asyncio-debug-mode), чтобы получить трассировку, где была создана задача. Пример записи в журнале в отладочном режиме:321322```python323Task was destroyed but it is pending!324source_traceback: Object created at (most recent call last):325  File "test.py", line 15, in <module>326    task = asyncio.ensure_future(coro, loop=loop)327task: <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>328```329330> **См. также**331>332> [*Обнаружение объектов корутин, которые никогда не планировались*](https://python-all.ru/3.4/library/asyncio-dev.html#asyncio-coroutine-not-scheduled).333334## 18.5.9.10. Закрытие транспортов и циклов событий335336Когда транспорт больше не нужен, следует вызвать его метод `close()` для освобождения ресурсов. Циклы событий также необходимо явно закрывать.337338Если транспорт или цикл событий не закрыт явно, [`ResourceWarning`](https://python-all.ru/3.4/library/exceptions.html#ResourceWarning) предупреждение будет выдано в его деструкторе. По умолчанию [`ResourceWarning`](https://python-all.ru/3.4/library/exceptions.html#ResourceWarning) предупреждения игнорируются. В разделе [*Режим отладки asyncio*](https://python-all.ru/3.4/library/asyncio-dev.html#asyncio-debug-mode) объясняется, как их отобразить.339