Фоновая задача ещё не готова: синхронизация asyncio через Event
Фоновый компонент сначала читает начальные данные, а потом начинает принимать работу. Вызывающий код делает create_task и сразу отправляет команду. Время от времени команда приходит раньше готовности. Добавление sleep(0.1) уменьшает частоту ошибки на одной машине, но не выражает причинную связь: сотая доля секунды ничего не говорит о завершении подготовки.
Нужен явный сигнал, который означает готовность. asyncio.Event хранит флаг: wait ждёт его установки, set будит ожидающих, а последующие wait проходят сразу, пока флаг не сброшен. Это не передача отдельного сообщения каждому потребителю, а публикация общего состояния. Для одноразового запуска такой смысл подходит.
Готовность как состояние
В примере компонент сначала добавляет запись о подготовке, устанавливает Event, затем ждёт остановки. Управляющая coroutine не угадывает длительность и не опрашивает список в цикле. Она дожидается ready и только после этого фиксирует приём работы. Второй Event задаёт завершение без бесконечного фонового цикла.
import asyncio
async def component(ready, stop, events):
events.append("prepared")
ready.set()
await stop.wait()
events.append("stopped")
async def main():
ready, stop = asyncio.Event(), asyncio.Event()
events = []
task = asyncio.create_task(component(ready, stop, events))
await ready.wait()
assert events == ["prepared"]
events.append("accepting")
stop.set()
await task
assert events == ["prepared", "accepting", "stopped"]
print(events)
asyncio.run(main())
Сигнал, ошибка и остановка
Сохраните файл и запустите Python 3.12. Ожидается последовательность prepared, accepting, stopped. Тест опирается на сигналы, поэтому результат не зависит от скорости диска или планировщика. Для негативной проверки перенесите ready.set перед подготовкой и добавьте между ними await asyncio.sleep(0): ожидающая задача получит управление до записи prepared, и assert обнаружит слишком раннее объявление готовности.
Определите поведение при ошибке подготовки. Если компонент упадёт до ready.set, вызывающий может ждать бесконечно. Для приложения удобно ждать либо готовность, либо завершение задачи, либо ограничивать весь запуск через внешний timeout и при отказе собирать ошибку фоновой задачи. Сам Event не переносит исключение от производителя к ожидающему.
Не используйте один set/clear как очередь команд. Несколько set подряд сливаются в установленный флаг, а не накапливают события. Для каждой отдельной работы подходит Queue. Если готовность может исчезать во время переподключения, заранее определите, что означает успешный wait: возможность начать операцию сейчас или подтверждение первоначального старта. У этих контрактов разные требования к повторной проверке состояния.
В тестах Event особенно полезен для управляемого пересечения задач. Одна задача может сообщить «дошла до чтения», а другая — разрешить продолжение. Это позволяет воспроизвести нужный порядок без произвольных задержек. Сигналы следует называть по смыслу состояния: prepared, connected, can_continue. Тогда тест показывает, какую зависимость по времени проверяет, и остаётся понятным после изменения скорости реализации.
Когда локальный пример работает, полезно разобрать соседние механизмы языка и их взаимодействие. В курсе «Python для профессионалов» есть практика по итераторам, генераторам, декораторам, asyncio, метапрограммированию и профилированию. Эти темы помогают понимать время жизни объектов, управление выполнением и причины поведения прикладной программы.
Посмотреть программу и практику курса.
Документация: официальный источник по теме.
Другие задачи по теме: Типы, тесты и поведение программы.
Продолжить на практике
- Python для профессионалов ↗
Python для профессионалов — курс для людей, стремящихся углубить свои знания языка. Вы изучите итераторы, генераторы, декораторы, современные возможности Python (3.8+), освоите метапрограммирование, асинхронность, внутреннее устройство интерпретатора, инструменты оптимизации и профилирования, а также получите уверенность в использовании продвинутых возможностей стандартной библиотеки.
