3.1 Modul asyncio
Eigene Threads für asynchrone Unteraufgaben erstellt heutzutage niemand mehr. Genauer gesagt, kann man sie erstellen, aber solche Aktionen gelten als zu niedrigstufig und werden nur von Framework-Entwicklern verwendet. Und auch dann, wenn es wirklich nicht anders geht.
Heute sind asynchrones Programmieren, die Operatoren async/await und Coroutinen mit Tasks angesagt. Aber der Reihe nach…
Ein bisschen Geschichte
Ursprünglich wurden in Python zur Lösung von Aufgaben des asynchronen Programmierens Coroutinen verwendet, die auf Generatoren basieren. Dann, in Python 3.4, wurde das Modul asyncio eingeführt (manchmal wird es auch als async IO bezeichnet), das Mechanismen für asynchrones Programmieren bietet. In Python 3.5 wurde die Konstruktion async/await eingeführt.
Und jetzt ein bisschen Vorgeschichte. Zuerst erkläre ich kurz all diese Dinge, dann ausführlicher und dann noch ausführlicher. Anders geht es nicht, da sie fast alle zusammenarbeiten und es detailliert zu erklären, ohne auf andere zu verweisen, nicht möglich ist.
Modul asyncio
Das Modul asyncio ist für das Schreiben von asynchronen Programmen gedacht und bietet die Möglichkeit, Aufgaben parallel auszuführen. Es unterstützt asynchrone Ein-/Ausgabe-Operationen, Timer, Sockets, die Ausführung von Coroutinen und Multithreading und arbeitet in einem oder mehreren Ereignisschleifen.
Coroutinen (Coroutines)
Coroutinen sind asynchrone Funktionen, die mit dem Schlüsselwort async def definiert werden. Coroutinen erlauben es, ihre Ausführung mit dem Schlüsselwort await zu pausieren, was anderen Coroutinen erlaubt, während dieser Zeit ausgeführt zu werden.
import asyncio
# Definition einer asynchronen Funktion (Coroutine)
async def main():
print('Hello ...')
# Ausführung für 1 Sekunde pausieren
await asyncio.sleep(1)
print('... World!')
# Ausführung der asynchronen Funktion main() in der Ereignisschleife
asyncio.run(main())
Ereignisschleife (Event Loop)
Die Ereignisschleife verwaltet die Ausführung von Coroutinen, Aufgaben und anderen asynchronen Operationen. Der Aufruf von asyncio.run() startet die Ereignisschleife und führt die Coroutine bis zum Abschluss aus.
import asyncio
async def main():
print('Hello ...')
await asyncio.sleep(1)
print('... World!')
# Aktuelle Ereignisschleife bekommen
loop = asyncio.get_event_loop()
# Coroutine bis zum Ende ausführen
loop.run_until_complete(main())
# Ereignisschleife nach Abschluss aller Aufgaben schließen
loop.close()
Aufgaben (Tasks)
Aufgaben erlauben es, Coroutinen parallel auszuführen. Sie werden mit Hilfe von asyncio.create_task() oder asyncio.ensure_future() erstellt.
import asyncio
# Definition einer Coroutine, die mit Verzögerung ausgeführt wird
async def say_after(delay, what):
# Ausführung für die angegebene Zeit pausieren
await asyncio.sleep(delay)
print(what)
# Hauptcoroutine
async def main():
# Aufgaben erstellen, um Coroutinen parallel auszuführen
task1 = asyncio.create_task(say_after(1, 'hello'))
task2 = asyncio.create_task(say_after(2, 'world'))
# Warten, bis beide Aufgaben abgeschlossen sind
await task1
await task2
# Hauptcoroutine starten
asyncio.run(main())
Futures (Futures)
Future-Objekte repräsentieren die Ergebnisse von asynchronen Operationen, die in der Zukunft verfügbar sein werden. Zum Beispiel werden sie verwendet, um auf den Abschluss einer asynchronen Aufgabe zu warten.
import asyncio
# Definition einer Coroutine, die eine langwierige Aufgabe simuliert
async def long_running_task():
print('Task started')
# Ausführung für 3 Sekunden pausieren
await asyncio.sleep(3)
print('Task finished')
return 'Result'
# Hauptcoroutine
async def main():
# Future erstellen, um auf den Abschluss der Aufgabe zu warten
future = asyncio.ensure_future(long_running_task())
# Warten, bis die Aufgabe abgeschlossen ist und Ergebnis bekommen
result = await future
print(f'Task result: {result}')
# Hauptcoroutine starten
asyncio.run(main())
3.2 Asynchrone Funktion — async def
Eine asynchrone Funktion wird genauso deklariert wie eine normale, nur dass vor dem Schlüsselwort def das Wort async stehen muss.
async def FunktionsName(parameter):
funktionscode
Eine asynchrone Funktion wird wie eine normale deklariert, wie eine normale aufgerufen, aber das Ergebnis, das sie zurückgibt, ist anders. Wenn man eine asynchrone Funktion aufruft, gibt sie nicht das Ergebnis zurück, sondern ein spezielles Objekt — eine Coroutine.
Man kann das sogar überprüfen:
import asyncio
async def main():
print("Hello World")
# Aufruf einer asynchronen Funktion, die eine Coroutine zurückgibt
result = main()
# Überprüfen des Typs des Ergebnisses
print(type(result)) # <class 'coroutine'>
Was passiert da? Wenn du eine Funktion mit dem Wort async markierst, fügst du ihr im Wesentlichen einen Dekorator hinzu, der ungefähr Folgendes macht:
def async_decorator(func):
# Task-Objekt erstellen
task = Task()
# Übergabe unserer Funktion func an das Task-Objekt, damit es sie ausführt
task.target = func
# Task-Objekt zur Aufgabenwarteschlange — Event Loop — hinzufügen
eventloop.add_task(task)
# Task-Objekt zurückgeben
return task
Und dein Code wird so ähnlich:
import asyncio
@async_decorator
def main():
print("Hello World")
result = main()
print(type(result)) # <class 'coroutine'>
Der Sinn dieser Analogie ist folgender:
Wenn du eine asynchrone Funktion aufrufst, wird ein spezielles Task-Objekt erstellt, das deine Funktion ausführen wird, aber irgendwann in der Zukunft. Vielleicht nach 0.0001 Sekunde oder vielleicht nach 10 Sekunden.
Dieses Objekt task wird dir sofort als Ergebnis des Aufrufs deiner asynchronen Funktion zurückgegeben. Das ist die Coroutine. Deine asynchrone Funktion hat vielleicht noch gar nicht angefangen zu laufen, aber das task (Coroutine)-Objekt hast du schon.
Wofür ist dir dieses task (Coroutine)? Du kannst mit ihm nicht viel anfangen, aber du kannst 3 Dinge tun:
- Warten, bis die asynchrone Funktion ausgeführt wird.
- Warten, bis die asynchrone Funktion fertig ist und das Ergebnis der Funktion aus der Coroutine erhalten.
- Warten, bis 10 (beliebige Anzahl) asynchrone Funktionen ausgeführt sind.
Wie du das machst, erkläre ich weiter unten.
3.3 Operator await
Ein Großteil der Aktionen mit einer Coroutine beginnt mit „warten auf die Ausführung der asynchronen Funktion“. Deshalb gibt es dafür einen speziellen Operator await.
Du musst ihn einfach vor die Coroutine schreiben:
await coroutine
Oder direkt vor den Aufruf der asynchronen Funktion:
await asynchrone_funktion(argumente)
Wenn Python den Operator await im Code trifft, pausiert es die Ausführung der aktuellen Funktion und wartet, bis die Coroutine abgeschlossen ist — bis die asynchrone Funktion, auf die sich die Coroutine bezieht, beendet ist.
Wichtig! Der Operator await wird nur innerhalb einer asynchronen Funktion verwendet, um die Ausführung zu pausieren, bis eine andere Coroutine oder asynchrone Operation abgeschlossen ist.
Das vereinfacht den Prozess des Umschaltens zwischen Aufrufen von asynchronen Funktionen. Ein solcher await-Aufruf ist eigentlich eine Deklaration „wir werden hier für unbestimmte Zeit warten — kümmert euch um die Ausführung anderer asynchroner Funktionen“.
Beispiel:
import asyncio
# Definition einer asynchronen Funktion
async def async_print(text):
print(text)
# Haupt asynchrone Funktion
async def main():
# Verwendung von await, um auf die Ausführung der asynchronen Funktion zu warten
await async_print("Hello World")
# Starten der Haupt-Ereignisschleife und Ausführung der Coroutine main()
asyncio.run(main()) #startet die asynchrone Funktion
Tatsächlich arbeitet der Operator await noch schlauer — er gibt auch das Ergebnis der asynchronen Funktion zurück, bei der er aufgerufen wurde.
Beispiel:
import asyncio
# Definition einer asynchronen Funktion, die zwei Zahlen addiert
async def async_add(a, b):
return a + b
# Haupt asynchrone Funktion
async def main():
# Verwendung von await, um das Ergebnis der Ausführung von async_add zu erhalten
sum = await async_add(100, 200)
print(sum)
# Starten der Haupt-Ereignisschleife und Ausführung der Coroutine main()
asyncio.run(main()) #startet die asynchrone Funktion
Fassen wir zusammen, der Operator await:
- Pausiert die aktuelle asynchrone Funktion, bis eine andere Coroutine oder asynchrone Operation abgeschlossen ist.
- Gibt das Ergebnis der asynchronen Operation oder Coroutine zurück.
- Kann nur innerhalb einer asynchronen Funktion verwendet werden.
GO TO FULL VERSION