Задача ждёт нужное состояние: asyncio.Condition вместо опроса
Потребителю нужно дождаться, пока в памяти появятся данные определённой версии. Проверка в цикле с sleep даёт выбор между лишней задержкой и постоянными пробуждениями. Event помогает дождаться факта, но сам по себе не связывает сигнал с защищённым чтением сложного состояния. Если одновременно меняются версия и содержимое, полезнее ждать предикат под общей блокировкой.
asyncio.Condition соединяет Lock и уведомления. Задача входит в async with, проверяет условие через wait_for и при необходимости освобождает блокировку на время ожидания. Перед возвратом блокировка снова захвачена. Производитель меняет состояние под той же Condition и вызывает notify_all. Читатель после пробуждения повторно проверяет свой предикат.
Ждём предикат под блокировкой
В примере ждём опубликованную версию не ниже второй. Словарь и Condition принадлежат одному event loop. Публикация меняет данные и версию в одной критической секции. Тест может создать ожидающего до публикации и получить результат без предположений о том, сколько миллисекунд потребуется производителю.
import asyncio
async def main():
condition = asyncio.Condition()
state = {"version": 0, "text": ""}
async def wait_version(version):
async with condition:
await condition.wait_for(lambda: state["version"] >= version)
return state["text"]
async def publish():
async with condition:
state.update(version=2, text="final")
condition.notify_all()
waiter = asyncio.create_task(wait_version(2))
await publish()
result = await waiter
assert result == "final"
print(result)
asyncio.run(main())
Публикация и повторная проверка
После запуска Python 3.12 печатает final. Если публикация произойдёт раньше начала wait_for, функция сразу увидит подходящее состояние. В этом и смысл предиката: результат зависит от данных, а не от того, успела ли задача поймать отдельное уведомление. Для проверки можно сначала вызвать publish и лишь потом wait_version — результат должен сохраниться.
notify_all не завершает ожидание окончательно. Он даёт задачам возможность снова получить lock и проверить условие. Поэтому вызывайте уведомление после изменения данных, а не вместо него. Если потребители ждут разные версии, часть из них продолжит ждать. Обычный notify одного ожидающего может разбудить задачу, которой новая версия пока не подходит; notify_all проще для такого небольшого реестра.
Держите внутри критической секции только действия над согласованным состоянием. Тяжёлое построение нового снимка можно выполнить заранее, а затем под lock заменить ссылку и обновить версию. Читателю стоит возвращать неизменяемый снимок или копию, если дальнейшие изменения общего объекта нарушат его ожидания.
Для очереди отдельных заданий Condition обычно не нужна: Queue уже даёт подходящую модель. Для ожидания «кэш обновлён до версии», «соединение перешло в ready», «освободилось достаточно локального ресурса» предикат делает контракт яснее. Тестируйте обе последовательности — ожидание до изменения и после него. Так проверяется отсутствие зависимости от случайного порядка запуска задач.
Когда локальный пример работает, полезно разобрать соседние механизмы языка и их взаимодействие. В курсе «Python для профессионалов» есть практика по итераторам, генераторам, декораторам, asyncio, метапрограммированию и профилированию. Эти темы помогают понимать время жизни объектов, управление выполнением и причины поведения прикладной программы.
Посмотреть программу и практику курса.
Документация: официальный источник по теме.
Другие задачи по теме: Типы, тесты и поведение программы.
Продолжить на практике
- Python для профессионалов ↗
Python для профессионалов — курс для людей, стремящихся углубить свои знания языка. Вы изучите итераторы, генераторы, декораторы, современные возможности Python (3.8+), освоите метапрограммирование, асинхронность, внутреннее устройство интерпретатора, инструменты оптимизации и профилирования, а также получите уверенность в использовании продвинутых возможностей стандартной библиотеки.
