8.7. Events¶
asyncio.Event ist das einfachste Signalisierungsprimitiv, das das Modul bereitstellt. Eine Koroutine setzt ein Event, wenn etwas passiert ist; beliebig viele andere Koroutinen warten darauf, dass das Event gesetzt wird, bevor sie fortfahren. Es gibt keine Nutzlast – ein Event ist ein boolescher Wert, der auf ein und an bleibt, bis etwas ihn löscht.
8.7.1. Die grundlegende Vorgehensweise¶
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())
Der wartende Task läuft, erreicht await evt.wait() und wird angehalten – das Event beginnt im gelöschten Zustand, sodass der wait-Aufruf die Kontrolle an die Schleife zurückgibt. Eine Sekunde später ruft main evt.set() auf und der Wartende wird zum Fortsetzen eingeplant. Das abschließende await asyncio.sleep(0) ist lediglich ein Yield, damit die Schleife die Gelegenheit erhält, den Wartenden auszuführen, bevor main zurückkehrt.
8.7.2. Die vier Methoden¶
set()– setzt das Event und plant jede Koroutine, die aktuell aufwait()blockiert ist, zum Fortsetzen ein. Das Event bleibt gesetzt, bis es gelöscht wird.clear()– setzt das Event wieder auf gelöscht zurück. Koroutinen, die danachawait wait()aufrufen, blockieren erneut.wait()– eine Koroutine. Ist das Event bereits gesetzt, kehrt sie sofort zurück. Andernfalls blockiert sie, bis etwas es setzt.is_set()– gibt den aktuellen Zustand zurück, ohne zu blockieren. Nützlich, wenn eine Koroutine das Event prüfen möchte, ohne darauf zu warten.
8.7.3. Mehrere Wartende¶
Mehrere Koroutinen können auf dasselbe Event awaiten. Wenn etwas set() aufruft, werden sie alle zum Fortsetzen eingeplant. Das Event ist standardmäßig eins-zu-viele; es besteht keine Notwendigkeit, mehrere Events zu verwenden, um an mehrere Wartende zu verteilen.
8.7.4. Ein Event wiederverwenden¶
Ein häufiges Muster besteht darin, ein Event wiederholt zu verwenden – eine Koroutine, die einmal pro Signal läuft –, indem man es nach jedem Durchlauf löscht:
async def worker(go):
while True:
await go.wait()
do_one_unit_of_work()
go.clear()
Der Produzent setzt das Event, sobald es Arbeit gibt; der Worker löscht es, sobald er das Signal aufgenommen hat. Falls der Produzent das Event erneut setzen könnte, bevor der Worker aufgewacht ist, möchte der Worker diesen Fall möglicherweise behandeln (while go.is_set(): ...), bevor er es löscht.
8.7.5. Sichere Kontexte¶
Bei Event ist es nicht sicher, set() aus einem Interrupt-Handler heraus aufzurufen. Der Mechanismus, mit dem es Wartende einplant, geht davon aus, dass er innerhalb des Kontexts der Ereignisschleife selbst läuft, was bei Interrupts nicht der Fall ist. Um einen asyncio-Task aus einem Interrupt-Handler aufzuwecken, ist ThreadSafeFlag (in Kürze behandelt) das richtige Primitiv.