Порядок вызовов asyncio.Event.set() гарантированно соответствует порядку пробуждения висящих на них корутин?

Подробнее:
Есть n asyncio.Event, на каждом из них висит ровно одна корутина (await asyncio.Event.wait()).
Метод asyncio.Event.set() - не корутина, и его вызов сразу же не отдает управление разбуженной корутине.
Вопрос: если я в цикле вызову на всех ивентах set(), то разбуженные корутины гарантированно начнут работу в том же порядке, в каком я вызвал set() на соответсвтующем ивенте?
Вот пример:
import asyncio


async def coro(n, event):
    await event.wait()
    print(n)


async def main():
    events = []
    for i in range(10):
        event = asyncio.Event()
        events.append(event)
        asyncio.create_task(coro(i, event))

    await asyncio.sleep(2)

    for ev in events[5:] + events[:5]:   # (1) 
        ev.set()

    print('all events are set')

    await asyncio.sleep(2)  # да, я знаю, что не так нужно дожидаться завершения корутин 


if __name__ == '__main__':
    asyncio.run(main())

В строке (1) намеренно изменен порядок вызовов set() и в итоге я получаю в stdout такой порядок чисел: (5,6,7,8,9,0,1,2,3,4), который полностью соответствует порядку вызовов set().
Хотя я запускал этот код не один раз и получал один и тот же результат, все же хочется быть уверенным на 100%, т.е. иметь пруфы, т.к. в моем проекте это важно. Может кто знает где это поведение описывается?
  • Вопрос задан
  • 859 просмотров
Пригласить эксперта
Ответы на вопрос 1
sgjurano
@sgjurano
Разработчик
А как порядок может быть другим? У вас же кооперативная многозадачность.

1) Вы в цикле насоздавали тасков, каждый из которых - это легковесный тред под управлением планировщика asyncio, каждый из них при запуске отдаёт управление обратно планировщику, добавляясь в список на оповещение по event.
2) Потом вы отдали управление планировщику на 2 секунды (зачем?)
3) Вы в фиксированном порядке вызываете ev.set(), что приводит к точно такому-же фиксированному порядку оповещения подписчиков.

Вообще такие штуки надо смотреть в коде, только там можно узнать правду :)

def set(self):
    """Set the internal flag to true. All coroutines waiting for it to
    become true are awakened. Coroutine that call wait() once the flag is
    true will not block at all.
    """
    if not self._value:
        self._value = True

        for fut in self._waiters:
            if not fut.done():
                fut.set_result(True)

@coroutine
def wait(self):
    """Block until the internal flag is true.

    If the internal flag is true on entry, return True
    immediately.  Otherwise, block until another coroutine calls
    set() to set the flag to true, then return True.
    """
    if self._value:
        return True

    fut = self._loop.create_future()
    self._waiters.append(fut)
    try:
        yield from fut
        return True
    finally:
        self._waiters.remove(fut)
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы