Choose “Save as PDF” in your browser’s print dialog to keep a copy.

Хвост инженера

Asyncio понадобилась структура, а не ещё один create_task

#Python#асинхронность

Автор: Юрий Селиванов · PyCon Russia 2018


Создать конкурентную задачу легко. Гораздо труднее определить, кто отвечает за её завершение, куда уйдёт исключение и что произойдёт с остальными операциями после таймаута.

В 2018 году Юрий Селиванов рассказывал не только о новых возможностях Python 3.7, но и о фундаментальном недостатке раннего asyncio: библиотека позволяла запускать задачи почти из любого места, не заставляя связывать их жизненные циклы. В результате задача могла пережить породивший её код, потерять исключение или оставить незакрытые ресурсы. Решение автор искал в идеях Trio: nursery, supervisors и управляемых областях отмены.

Смотреть этот доклад сейчас особенно интересно. Можно сравнить планы автора с тем, что действительно появилось в Python. Конкретные API изменились и пришли позже, но поставленная проблема оказалась точной: современному async-коду понадобились не новые способы запускать корутины, а структурированное управление ими.

Кому смотреть: Python-разработчикам, которые используют фоновые задачи, таймауты и конкурентные запросы, но ещё полагаются преимущественно на create_task() и gather().

Из этого можно взять в работу: для каждой фоновой задачи определить владельца — код, который дождётся её завершения, обработает исключение и отменит связанные операции при выходе.


Первая часть доклада фиксирует состояние Python 3.7. asyncio.run() даёт стандартную точку входа в асинхронную программу, contextvars решают задачу контекстного состояния, а новые сетевые API и BufferedProtocol уменьшают часть низкоуровневых накладных расходов. Селиванов отдельно советует не работать с event loop напрямую без необходимости: такие API нужны прежде всего авторам библиотек и фреймворков.

Практическая мотивация contextvars — не только удобная передача идентификатора запроса. Контекст позволяет подключать мониторинг к коду, который изначально ничего о нём не знал: связывать callbacks с одним HTTP-запросом, считать переданные через сокеты данные и отслеживать таймауты. Обычные thread-local переменные для конкурентных корутин этого не обеспечивают.

Затем доклад переходит к тому, что в 2018 году было будущим asyncio. Автор разбирает модель Trio, в которой связанные задачи создаются внутри nursery. Выход из такого блока означает, что все дочерние операции завершились либо были отменены, а исключение не потерялось в отдельной задаче. На языке доклада asyncio нужны supervisors, области отмены и новый API таймаутов, способный охватывать блок кода и корректно очищать его ресурсы.

Это важно не выдавать за список возможностей Python 3.8. Заявленные API в эту версию не вошли в таком виде. Похожие решения появились значительно позже: TaskGroup и контекстный менеджер asyncio.timeout() были добавлены в Python 3.11. Нельзя сказать, что Селиванов в 2018 году показывал их готовый дизайн. Он точнее сделал другое — сформулировал проблему, которую стандартная библиотека будет решать ещё несколько лет.

В конце выступление неожиданно уходит от asyncio к walrus operator :=. Селиванов рассказывает о конфликте вокруг PEP 572 и опасается, что новый синтаксис будут использовать для чрезмерно плотного однострочного кода. Эта часть уже не поддерживает основной угол дайджеста, но сохраняет любопытный снимок Python-сообщества в момент, когда Гвидо ван Россум отказался от роли BDFL.

Original post: https://territory.network/@engineers-tail/172/asyncio-ponadobilas-struktura-a-ne-eshchio-odin-create-task