8.7. Zdarzenia#

asyncio.Event to najprostszy prymityw sygnalizacyjny udostępniany przez moduł. Jedna korutyna ustawia zdarzenie, gdy coś się wydarzyło; dowolna liczba innych korutyn czeka na ustawienie zdarzenia przed kontynuowaniem. Nie ma ładunku – zdarzenie jest wartością logiczną, która przełącza się na włączoną i pozostaje włączona, dopóki coś jej nie wyczyści.

8.7.1. Podstawowy schemat#

import asyncio

async def waiter(evt):
    print("waiting")
    await evt.wait()
    print("got signal")

async def main():
    evt = asyncio.Event()
    asyncio.create_task(waiter(evt))
    await asyncio.sleep(1)
    evt.set()
    await asyncio.sleep(0)

asyncio.run(main())

Zadanie oczekujące działa, trafia na await evt.wait() i zostaje wstrzymane – zdarzenie zaczyna w stanie wyczyszczonym, więc wywołanie wait oddaje sterowanie z powrotem do pętli. Sekundę później main wywołuje evt.set() i oczekujące zostaje zaplanowane do wznowienia. Końcowe await asyncio.sleep(0) to po prostu oddanie sterowania, aby pętla dostała szansę uruchomić oczekującego, zanim main powróci.

8.7.2. Cztery metody#

  • set() – przełącza zdarzenie na ustawione i planuje wznowienie każdej korutyny aktualnie zablokowanej na wait(). Zdarzenie pozostaje ustawione aż do wyczyszczenia.

  • clear() – przełącza zdarzenie z powrotem na wyczyszczone. Korutyny, które po tym wykonają await wait(), ponownie się zablokują.

  • wait() – korutyna. Jeśli zdarzenie jest już ustawione, zwraca sterowanie natychmiast. W przeciwnym razie blokuje, dopóki coś go nie ustawi.

  • is_set() – zwraca bieżący stan bez blokowania. Przydatne, gdy korutyna chce sprawdzić zdarzenie bez oczekiwania na nie.

8.7.3. Wielu oczekujących#

Kilka korutyn może wykonać await na tym samym zdarzeniu. Gdy coś wywoła set(), wszystkie zostają zaplanowane do wznowienia. Zdarzenie jest domyślnie jeden-do-wielu; nie ma potrzeby tworzenia kilku zdarzeń, by rozprowadzić je do kilku oczekujących.

8.7.4. Ponowne użycie zdarzenia#

Powszechnym wzorcem jest wielokrotne użycie zdarzenia – korutyna działająca raz na sygnał – poprzez czyszczenie go po każdym przebiegu:

async def worker(go):
    while True:
        await go.wait()
        do_one_unit_of_work()
        go.clear()

Producent ustawia zdarzenie, gdy tylko jest praca; worker czyści je, gdy odbierze sygnał. Jeśli producent może ponownie ustawić zdarzenie, zanim worker się obudzi, worker może chcieć obsłużyć ten przypadek (while go.is_set(): ...) przed czyszczeniem.

8.7.5. Bezpieczne konteksty#

Na Event nie można bezpiecznie wywołać set() z wnętrza funkcji obsługi przerwania. Mechanizm, którego używa do planowania oczekujących, zakłada, że działa wewnątrz własnego kontekstu pętli zdarzeń, czym przerwania nie są. Do wybudzania zadania asyncio z funkcji obsługi przerwania właściwym prymitywem jest ThreadSafeFlag (omówiony wkrótce).