Задача ждёт нужное состояние: 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+), освоите метапрограммирование, асинхронность, внутреннее устройство интерпретатора, инструменты оптимизации и профилирования, а также получите уверенность в использовании продвинутых возможностей стандартной библиотеки.