Das Modul asyncio wurde in Python 3.4 zunächst als vorläufiges Paket hinzugefügt. Das bedeutet, dass asyncio rückwärtsinkompatible Änderungen erhalten oder in einer zukünftigen Python-Version sogar entfernt werden könnte.
Laut Dokumentation „bietet asyncio die Infrastruktur, um nebenläufigen Single-Thread-Code mit Coroutinen zu schreiben, I/O-Zugriffe über Sockets und andere Ressourcen zu multiplexen, Netzwerk-Clients und -Server auszuführen und weitere verwandte Primitive bereitzustellen“. Dieses Kapitel will nicht alles abdecken, was mit asyncio möglich ist, aber du lernst, wie das Modul funktioniert und warum es nützlich ist.
Wenn du etwas wie asyncio für eine ältere Python-Version brauchst, wirf einen Blick auf Twisted oder gevent.
Begriffe
Das asyncio-Modul stellt ein Framework bereit, dessen Kern der Event-Loop ist. Ein Event-Loop wartet im Grunde darauf, dass etwas passiert, und reagiert dann auf das Ereignis. Er ist zuständig für Dinge wie I/O- und Systemereignisse.
asyncio bringt tatsächlich mehrere Loop-Implementierungen mit. Standardmäßig wählt das Modul die für das jeweilige Betriebssystem voraussichtlich effizienteste aus; du kannst den Event-Loop aber auch explizit festlegen. Ein Event-Loop folgt dem Prinzip: „Wenn Ereignis A eintritt, reagiere mit Funktion B“.
Denk an einen Server, der darauf wartet, dass jemand eine Ressource anfragt, zum Beispiel eine Webseite. Ist die Seite nicht sehr populär, ist der Server lange untätig. Kommt eine Anfrage, muss er reagieren. Diese Reaktion nennt man Event-Handling. Wenn ein User die Seite lädt, prüft der Server einen oder mehrere Event-Handler und ruft sie auf. Sobald diese fertig sind, geben sie die Kontrolle an den Event-Loop zurück. In Python nutzt asyncio dafür Coroutinen.
Eine Coroutine ist eine spezielle Funktion, die die Kontrolle an ihren Aufrufer abgeben kann, ohne ihren Zustand zu verlieren. Sie ist ein Verbraucher und eine Weiterentwicklung eines Generators. Ein großer Vorteil gegenüber Threads: Sie benötigen sehr wenig Speicher. Beachte: Wenn du eine Coroutine-Funktion aufrufst, wird sie nicht sofort ausgeführt. Stattdessen gibt sie ein Coroutine-Objekt zurück, das du dem Event-Loop übergeben kannst – zur sofortigen oder späteren Ausführung.
Ein weiterer Begriff, dem du bei asyncio begegnest, ist Future. Eine Future ist ein Objekt, das das Ergebnis einer noch nicht abgeschlossenen Arbeit repräsentiert. Dein Event-Loop kann Futures beobachten und auf deren Abschluss warten. Wenn eine Future fertig ist, steht sie auf done. Asyncio unterstützt außerdem Locks und Semaphore.
Zuletzt noch die Task. Eine Task ist ein Wrapper um eine Coroutine und eine Unterklasse von Future. Tasks lassen sich auch über den Event-Loop planen.
async und await
Die Schlüsselwörter async und await kamen mit Python 3.5 hinzu, um native Coroutinen zu definieren und sie klar von generatorbasierten Coroutinen abzugrenzen. Für eine detaillierte Beschreibung von async und await lies PEP 492.
In Python 3.4 würdest du eine Coroutine so erstellen:
# Python 3.4 coroutine example
# Import `asyncio`
import _______
@asyncio.coroutine
def my_coro():
yield from func()
Dieser Decorator funktioniert auch in Python 3.5 noch, aber das types-Modul hat eine Erweiterung in Form einer coroutine function erhalten, die dir nun sagen kann, ob es sich um eine native Coroutine handelt oder nicht. Ab Python 3.5 kannst du mit async def eine Coroutine-Funktion auch syntaktisch definieren.
Die Funktion von oben sähe dann so aus:
import _______
async def _______():
await func()
Wenn du eine Coroutine auf diese Weise definierst, kannst du innerhalb der Funktion kein yield verwenden. Stattdessen muss sie eine return- oder await-Anweisung enthalten, um Werte an den Aufrufer zurückzugeben. Beachte: Das Schlüsselwort await darf nur in einer async def-Funktion verwendet werden.
Die Schlüsselwörter async/await kannst du als API für asynchrones Programmieren betrachten. Das asyncio-Modul ist „nur“ ein Framework, das async/await für asynchrone Programmierung nutzt. Es gibt sogar ein Projekt namens curio, das dieses Konzept belegt – eine separate Event-Loop-Implementierung, die unter der Haube async/await verwendet.
Ein schlechtes Coroutine-Beispiel
Hintergrundwissen ist hilfreich, aber manchmal willst du einfach Beispiele sehen, um ein Gefühl für die Syntax und das Zusammenspiel der Teile zu bekommen.
Fangen wir also mit einem einfachen Beispiel an!
Eine ziemlich typische Aufgabe ist das Herunterladen einer Datei – sei es aus einer internen Quelle oder aus dem Internet. Meistens willst du gleich mehrere Dateien herunterladen.
Lass uns dafür zwei Coroutinen schreiben:
import asyncio
import os
import urllib.request
async def download_coroutine(url):
#"A coroutine to download the specified url"
request = urllib.request.urlopen(url)
filename = os.path.basename(url)withopen(filename,'wb')asfile_handle:whileTrue:
chunk = request.read(1024)if not chunk:break
file_handle.write(chunk)
msg ='Finished downloading {filename}'.format(filename=filename)return msg
async def main(urls):"""
Creates a group of coroutines and waits for them to finish
"""
coroutines =[download_coroutine(url)for url in urls]
completed, pending =await asyncio.wait(coroutines)for item incompleted:print(item.result())if __name__ =='__main__':
urls =["http://www.irs.gov/pub/irs-pdf/f1040.pdf","http://www.irs.gov/pub/irs-pdf/f1040a.pdf","http://www.irs.gov/pub/irs-pdf/f1040ez.pdf","http://www.irs.gov/pub/irs-pdf/f1040es.pdf","http://www.irs.gov/pub/irs-pdf/f1040sb.pdf"]
event_loop = asyncio.get_event_loop()try:
event_loop.run_until_complete(main(urls))finally:
event_loop.close()Bereitgestellt von
Hier importieren wir die benötigten Module und erstellen mit der async-Syntax unsere erste Coroutine. Diese Coroutine heißt download_coroutine und nutzt Pythons urllib, um die übergebene URL herunterzuladen. Ist sie fertig, gibt sie eine entsprechende Meldung zurück.
Die andere Coroutine ist unsere Main-Coroutine. Sie nimmt eine Liste aus einer oder mehreren URLs, reiht sie in die Warteschlange ein und wartet mit asyncio.wait auf deren Abschluss. Damit die Coroutinen überhaupt starten, müssen sie dem Event-Loop hinzugefügt werden. Das geschieht am Ende: Wir holen uns den Event-Loop und rufen seine Methode run_until_complete auf. Der Event-Loop erhält die Main-Coroutine, welche die zweite Coroutine einreiht und startet. Das nennt man eine verkettete Coroutine.
Das Problem an diesem Beispiel: Eigentlich ist es gar nicht asynchron. Der Grund ist, dass die Funktion download_coroutine nicht asynchron arbeitet. urllib ist nicht asynchron, und außerdem verwenden wir weder await noch yield. Besser wäre hier das Paket aiohttp. Schauen wir uns das als Nächstes an!
Ein besseres Coroutine-Beispiel
Das Paket aiohttp ist für die Erstellung asynchroner HTTP-Clients und -Server gedacht. Du kannst es mit pip so installieren:
Nachdem es installiert ist, passen wir unseren Code an aiohttp an, um die Dateien herunterzuladen:
import aiohttp
import asyncio
import async_timeout
import os
async def download_coroutine(session, url):
with async_timeout.timeout(10):
async with session.get(url) as response:
filename = __.path.basename(url)
with open(________, 'wb') as f_handle:
while True:
chunk = await response.content.read(1024)
if not chunk:
break
f_handle.write(_____)
return await response.release()
async def main(loop):
urls = ["http://www.irs.gov/pub/irs-pdf/f1040.pdf",
"http://www.irs.gov/pub/irs-pdf/f1040a.pdf",
"http://www.irs.gov/pub/irs-pdf/f1040ez.pdf",
"http://www.irs.gov/pub/irs-pdf/f1040es.pdf",
"http://www.irs.gov/pub/irs-pdf/f1040sb.pdf"]
async with aiohttp.ClientSession(loop=loop) as session:
tasks = [download_coroutine(session, url) for url in urls]
await asyncio.gather(*tasks)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main(loop))
Hier importieren wir zwei neue Bausteine: aiohttp und async_timeout. Letzteres ist eine Abhängigkeit von aiohttp und ermöglicht uns einen Timeout-Context-Manager. Gehen wir von unten nach oben durch den Code: In der unteren Bedingung starten wir den asynchronen Event-Loop und rufen unsere Main-Funktion auf.
In der Main-Funktion erstellen wir ein ClientSession-Objekt, das wir an unsere Download-Coroutine für jede zu ladende URL übergeben. In download_coroutine erzeugen wir mit async_timeout.timeout() einen Context-Manager, der im Grunde einen Timer von X Sekunden setzt. Läuft die Zeit ab, endet der Context-Manager. Hier beträgt das Timeout 10 Sekunden. Anschließend rufen wir get() auf der Session auf und erhalten ein Response-Objekt. Jetzt kommt der magische Teil: Über das Attribut content des Response-Objekts erhalten wir eine Instanz von aiohttp.StreamReader, mit der wir die Datei in frei wählbaren Chunk-Größen herunterladen können. Während wir lesen, schreiben wir auf die lokale Platte. Zum Schluss rufen wir release() der Response auf, um die Verarbeitung abzuschließen.
Laut aiohttp-Dokumentation wird release() implizit aufgerufen, da das Response-Objekt in einem Context-Manager erstellt wurde. In Python gilt jedoch meist: Explizit ist besser als implizit. Zudem rät die Doku davon ab, sich darauf zu verlassen, dass die Verbindung einfach verschwindet – daher ist es hier besser, explizit zu releasen.
Ein Teil blockiert hier weiterhin: das Schreiben auf die Festplatte. Während des Schreibens blockieren wir noch. Es gibt eine weitere Bibliothek namens aiofiles, mit der sich auch das Dateischreiben asynchronisieren ließe. Dieses Update überlasse ich dir.
Aufrufe planen
Du kannst mit dem asyncio-Event-Loop auch Aufrufe regulärer Funktionen planen. Die erste Methode ist call_soon. Sie ruft deinen Callback bzw. Event-Handler so schnell wie möglich auf. Die Ausführung folgt einer FIFO-Warteschlange. Dauern einige Callbacks länger, verzögern sich die nachfolgenden, bis die vorherigen fertig sind.
Die meisten Funktionen von asyncio akzeptieren keine Keyword-Argumente, daher brauchen wir für Schlüsselwörter das Modul functools. Unsere reguläre Funktion schreibt bei jedem Aufruf Text auf stdout. Setzt du das Argument stop auf True, stoppt sie außerdem den Event-Loop.
Beim ersten Aufruf stoppen wir den Loop nicht. Beim zweiten Aufruf stoppen wir ihn. Das ist nötig, weil wir run_forever aufgerufen haben – der Event-Loop würde sonst endlos laufen. Ist der Loop gestoppt, können wir ihn schließen.
Wenn du den Code startest, solltest du diese Ausgabe sehen:
starting event loop
Event handler called
Event handler called
stopping the loop
closing event loopBereitgestellt von
Verwandt ist die Funktion call_soon_threadsafe. Wie der Name vermuten lässt, funktioniert sie wie call_soon, ist aber thread-sicher. Wenn du einen Aufruf gezielt verzögern möchtest, kannst du call_later verwenden. In unserem Beispiel könnten wir die call_soon-Signatur so ändern:
Damit verzögern wir den Aufruf unseres Event-Handlers um eine Sekunde; anschließend wird er aufgerufen und erhält den Loop als ersten Parameter. Möchtest du einen konkreten Zeitpunkt in der Zukunft planen, nimm die Zeit des Loops statt der Systemzeit. So geht’s:
Dann kannst du call_at verwenden und die gewünschte Zeit übergeben. Angenommen, wir wollen den Event-Handler in fünf Minuten aufrufen. So könnte das aussehen:
Hier nehmen wir die aktuelle Loop-Zeit und addieren 300 Sekunden bzw. fünf Minuten. So verzögern wir den Aufruf unseres Event-Handlers um fünf Minuten. Ziemlich praktisch!
Tasks
Tasks sind eine Unterklasse von Future und ein Wrapper um eine Coroutine. Sie ermöglichen es dir, nachzuvollziehen, wann die Verarbeitung abgeschlossen ist. Da sie eine Future sind, können andere Coroutinen auf eine Task warten, und du kannst das Ergebnis einer fertig verarbeiteten Task abrufen.
Ein einfaches Beispiel:
import _______
async def my_task(seconds):
"""
A task to do for a number of seconds
"""
print('This task is taking {} seconds to complete'.format(
seconds))
await asyncio.sleep(_______)
return 'task finished'
if __name__ == '__main__':
my_event_loop = asyncio.get_event_loop()
try:
print('task creation started')
task_obj = my_event_loop.create_task(my_task(seconds=2))
my_event_loop.run_until_complete(task_obj)
finally:
my_event_loop._____()
print("The task's result was: {}".format(task_obj.result()))
Hier definieren wir eine asynchrone Funktion, die die Anzahl der Sekunden entgegennimmt, die sie „laufen“ soll – als Simulation eines lang laufenden Prozesses. Dann erstellen wir den Event-Loop und erzeugen mit dessen create_task eine Task. create_task nimmt die Funktion entgegen, die wir als Task ausführen wollen. Anschließend lassen wir den Event-Loop laufen, bis die Task abgeschlossen ist. Am Ende holen wir uns das Ergebnis der Task.
Tasks lassen sich außerdem sehr leicht mit ihrer cancel-Methode abbrechen. Ruf sie einfach auf, wenn du eine Task beenden willst. Wird eine Task während des Wartens auf eine andere Operation abgebrochen, wirft sie eine CancelledError.
Fazit
Jetzt kennst du genug, um mit der Bibliothek asyncio eigenständig zu arbeiten. asyncio ist sehr mächtig und ermöglicht dir eine Menge spannender Aufgaben. Die asyncio-Bibliothek wurde für Netzwerksockets konzipiert.
Eine großartige Bibliothek für asynchrones Socket-Programming vor asyncio ist das Twisted-Framework. Ein weiteres spannendes Projekt ist Dask, eine flexible Bibliothek für paralleles Rechnen in der Analytik. Stöbere in der Dokumentation zum asyncio-Modul und lass deinen Ideen freien Lauf!
Erforsche Pythons match-case: eine Anleitung zu seiner Syntax, Anwendungen in Data Science und ML sowie eine vergleichende Analyse mit dem traditionellen switch-case.
Ein umfassendes Einführungs-Tutorial zu Python-Schleifen. Lerne und übe while- und for-Schleifen, verschachtelte Schleifen, die Schlüsselwörter break und continue, die Range-Funktion und vieles mehr!
Lerne mehr über Python-Lambda-Funktionen, wozu sie gut sind und wann man sie benutzt. Enthält praktische Beispiele und bewährte Methoden für eine effektive Umsetzung.
Mach dich mit Python-Datenstrukturen vertraut: Lerne mehr über Datentypen und primitive sowie nicht-primitive Datenstrukturen wie Strings, Listen, Stapel usw.
Das Modul
asynciowurde in Python 3.4 zunächst als vorläufiges Paket hinzugefügt. Das bedeutet, dassasynciorückwärtsinkompatible Änderungen erhalten oder in einer zukünftigen Python-Version sogar entfernt werden könnte.Laut Dokumentation „bietet
asynciodie Infrastruktur, um nebenläufigen Single-Thread-Code mit Coroutinen zu schreiben, I/O-Zugriffe über Sockets und andere Ressourcen zu multiplexen, Netzwerk-Clients und -Server auszuführen und weitere verwandte Primitive bereitzustellen“. Dieses Kapitel will nicht alles abdecken, was mitasynciomöglich ist, aber du lernst, wie das Modul funktioniert und warum es nützlich ist.Wenn du etwas wie
asynciofür eine ältere Python-Version brauchst, wirf einen Blick auf Twisted oder gevent.Begriffe
Das
asyncio-Modul stellt ein Framework bereit, dessen Kern der Event-Loop ist. Ein Event-Loop wartet im Grunde darauf, dass etwas passiert, und reagiert dann auf das Ereignis. Er ist zuständig für Dinge wie I/O- und Systemereignisse.asynciobringt tatsächlich mehrere Loop-Implementierungen mit. Standardmäßig wählt das Modul die für das jeweilige Betriebssystem voraussichtlich effizienteste aus; du kannst den Event-Loop aber auch explizit festlegen. Ein Event-Loop folgt dem Prinzip: „Wenn Ereignis A eintritt, reagiere mit Funktion B“.Denk an einen Server, der darauf wartet, dass jemand eine Ressource anfragt, zum Beispiel eine Webseite. Ist die Seite nicht sehr populär, ist der Server lange untätig. Kommt eine Anfrage, muss er reagieren. Diese Reaktion nennt man Event-Handling. Wenn ein User die Seite lädt, prüft der Server einen oder mehrere Event-Handler und ruft sie auf. Sobald diese fertig sind, geben sie die Kontrolle an den Event-Loop zurück. In Python nutzt
asynciodafür Coroutinen.Eine Coroutine ist eine spezielle Funktion, die die Kontrolle an ihren Aufrufer abgeben kann, ohne ihren Zustand zu verlieren. Sie ist ein Verbraucher und eine Weiterentwicklung eines Generators. Ein großer Vorteil gegenüber Threads: Sie benötigen sehr wenig Speicher. Beachte: Wenn du eine Coroutine-Funktion aufrufst, wird sie nicht sofort ausgeführt. Stattdessen gibt sie ein Coroutine-Objekt zurück, das du dem Event-Loop übergeben kannst – zur sofortigen oder späteren Ausführung.
Ein weiterer Begriff, dem du bei
asynciobegegnest, ist Future. Eine Future ist ein Objekt, das das Ergebnis einer noch nicht abgeschlossenen Arbeit repräsentiert. Dein Event-Loop kann Futures beobachten und auf deren Abschluss warten. Wenn eine Future fertig ist, steht sie auf done. Asyncio unterstützt außerdem Locks und Semaphore.Zuletzt noch die Task. Eine Task ist ein Wrapper um eine Coroutine und eine Unterklasse von Future. Tasks lassen sich auch über den Event-Loop planen.
asyncundawaitDie Schlüsselwörter
asyncundawaitkamen mit Python 3.5 hinzu, um native Coroutinen zu definieren und sie klar von generatorbasierten Coroutinen abzugrenzen. Für eine detaillierte Beschreibung vonasyncundawaitlies PEP 492.In Python 3.4 würdest du eine Coroutine so erstellen:
Dieser Decorator funktioniert auch in Python 3.5 noch, aber das types-Modul hat eine Erweiterung in Form einer coroutine function erhalten, die dir nun sagen kann, ob es sich um eine native Coroutine handelt oder nicht. Ab Python 3.5 kannst du mit
async defeine Coroutine-Funktion auch syntaktisch definieren.Die Funktion von oben sähe dann so aus:
Wenn du eine Coroutine auf diese Weise definierst, kannst du innerhalb der Funktion kein
yieldverwenden. Stattdessen muss sie einereturn- oderawait-Anweisung enthalten, um Werte an den Aufrufer zurückzugeben. Beachte: Das Schlüsselwortawaitdarf nur in einerasync def-Funktion verwendet werden.Die Schlüsselwörter
async/awaitkannst du als API für asynchrones Programmieren betrachten. Das asyncio-Modul ist „nur“ ein Framework, dasasync/awaitfür asynchrone Programmierung nutzt. Es gibt sogar ein Projekt namenscurio, das dieses Konzept belegt – eine separate Event-Loop-Implementierung, die unter der Haubeasync/awaitverwendet.Ein schlechtes Coroutine-Beispiel
Hintergrundwissen ist hilfreich, aber manchmal willst du einfach Beispiele sehen, um ein Gefühl für die Syntax und das Zusammenspiel der Teile zu bekommen.
Fangen wir also mit einem einfachen Beispiel an!
Eine ziemlich typische Aufgabe ist das Herunterladen einer Datei – sei es aus einer internen Quelle oder aus dem Internet. Meistens willst du gleich mehrere Dateien herunterladen.
Lass uns dafür zwei Coroutinen schreiben:
Hier importieren wir die benötigten Module und erstellen mit der async-Syntax unsere erste Coroutine. Diese Coroutine heißt
download_coroutineund nutzt Pythonsurllib, um die übergebene URL herunterzuladen. Ist sie fertig, gibt sie eine entsprechende Meldung zurück.Die andere Coroutine ist unsere Main-Coroutine. Sie nimmt eine Liste aus einer oder mehreren URLs, reiht sie in die Warteschlange ein und wartet mit
asyncio.waitauf deren Abschluss. Damit die Coroutinen überhaupt starten, müssen sie dem Event-Loop hinzugefügt werden. Das geschieht am Ende: Wir holen uns den Event-Loop und rufen seine Methoderun_until_completeauf. Der Event-Loop erhält die Main-Coroutine, welche die zweite Coroutine einreiht und startet. Das nennt man eine verkettete Coroutine.Das Problem an diesem Beispiel: Eigentlich ist es gar nicht asynchron. Der Grund ist, dass die Funktion
download_coroutinenicht asynchron arbeitet.urllibist nicht asynchron, und außerdem verwenden wir wederawaitnochyield. Besser wäre hier das Paketaiohttp. Schauen wir uns das als Nächstes an!Ein besseres Coroutine-Beispiel
Das Paket
aiohttpist für die Erstellung asynchroner HTTP-Clients und -Server gedacht. Du kannst es mit pip so installieren:Nachdem es installiert ist, passen wir unseren Code an
aiohttpan, um die Dateien herunterzuladen:Hier importieren wir zwei neue Bausteine:
aiohttpundasync_timeout. Letzteres ist eine Abhängigkeit vonaiohttpund ermöglicht uns einen Timeout-Context-Manager. Gehen wir von unten nach oben durch den Code: In der unteren Bedingung starten wir den asynchronen Event-Loop und rufen unsere Main-Funktion auf.In der Main-Funktion erstellen wir ein
ClientSession-Objekt, das wir an unsere Download-Coroutine für jede zu ladende URL übergeben. Indownload_coroutineerzeugen wir mitasync_timeout.timeout()einen Context-Manager, der im Grunde einen Timer von X Sekunden setzt. Läuft die Zeit ab, endet der Context-Manager. Hier beträgt das Timeout 10 Sekunden. Anschließend rufen wirget()auf der Session auf und erhalten ein Response-Objekt. Jetzt kommt der magische Teil: Über das Attribut content des Response-Objekts erhalten wir eine Instanz vonaiohttp.StreamReader, mit der wir die Datei in frei wählbaren Chunk-Größen herunterladen können. Während wir lesen, schreiben wir auf die lokale Platte. Zum Schluss rufen wirrelease()der Response auf, um die Verarbeitung abzuschließen.Laut
aiohttp-Dokumentation wirdrelease()implizit aufgerufen, da das Response-Objekt in einem Context-Manager erstellt wurde. In Python gilt jedoch meist: Explizit ist besser als implizit. Zudem rät die Doku davon ab, sich darauf zu verlassen, dass die Verbindung einfach verschwindet – daher ist es hier besser, explizit zu releasen.Ein Teil blockiert hier weiterhin: das Schreiben auf die Festplatte. Während des Schreibens blockieren wir noch. Es gibt eine weitere Bibliothek namens
aiofiles, mit der sich auch das Dateischreiben asynchronisieren ließe. Dieses Update überlasse ich dir.Aufrufe planen
Du kannst mit dem asyncio-Event-Loop auch Aufrufe regulärer Funktionen planen. Die erste Methode ist
call_soon. Sie ruft deinen Callback bzw. Event-Handler so schnell wie möglich auf. Die Ausführung folgt einer FIFO-Warteschlange. Dauern einige Callbacks länger, verzögern sich die nachfolgenden, bis die vorherigen fertig sind.Ein Beispiel:
Die meisten Funktionen von
asyncioakzeptieren keine Keyword-Argumente, daher brauchen wir für Schlüsselwörter das Modul functools. Unsere reguläre Funktion schreibt bei jedem Aufruf Text auf stdout. Setzt du das ArgumentstopaufTrue, stoppt sie außerdem den Event-Loop.Beim ersten Aufruf stoppen wir den Loop nicht. Beim zweiten Aufruf stoppen wir ihn. Das ist nötig, weil wir
run_foreveraufgerufen haben – der Event-Loop würde sonst endlos laufen. Ist der Loop gestoppt, können wir ihn schließen.Wenn du den Code startest, solltest du diese Ausgabe sehen:
Verwandt ist die Funktion
call_soon_threadsafe. Wie der Name vermuten lässt, funktioniert sie wiecall_soon, ist aber thread-sicher. Wenn du einen Aufruf gezielt verzögern möchtest, kannst ducall_laterverwenden. In unserem Beispiel könnten wir diecall_soon-Signatur so ändern:Damit verzögern wir den Aufruf unseres Event-Handlers um eine Sekunde; anschließend wird er aufgerufen und erhält den Loop als ersten Parameter. Möchtest du einen konkreten Zeitpunkt in der Zukunft planen, nimm die Zeit des Loops statt der Systemzeit. So geht’s:
Dann kannst du
call_atverwenden und die gewünschte Zeit übergeben. Angenommen, wir wollen den Event-Handler in fünf Minuten aufrufen. So könnte das aussehen:Hier nehmen wir die aktuelle Loop-Zeit und addieren 300 Sekunden bzw. fünf Minuten. So verzögern wir den Aufruf unseres Event-Handlers um fünf Minuten. Ziemlich praktisch!
Tasks
Tasks sind eine Unterklasse von Future und ein Wrapper um eine Coroutine. Sie ermöglichen es dir, nachzuvollziehen, wann die Verarbeitung abgeschlossen ist. Da sie eine Future sind, können andere Coroutinen auf eine Task warten, und du kannst das Ergebnis einer fertig verarbeiteten Task abrufen.
Ein einfaches Beispiel:
Hier definieren wir eine asynchrone Funktion, die die Anzahl der Sekunden entgegennimmt, die sie „laufen“ soll – als Simulation eines lang laufenden Prozesses. Dann erstellen wir den Event-Loop und erzeugen mit dessen
create_taskeine Task.create_tasknimmt die Funktion entgegen, die wir als Task ausführen wollen. Anschließend lassen wir den Event-Loop laufen, bis die Task abgeschlossen ist. Am Ende holen wir uns das Ergebnis der Task.Tasks lassen sich außerdem sehr leicht mit ihrer
cancel-Methode abbrechen. Ruf sie einfach auf, wenn du eine Task beenden willst. Wird eine Task während des Wartens auf eine andere Operation abgebrochen, wirft sie eineCancelledError.Fazit
Jetzt kennst du genug, um mit der Bibliothek
asyncioeigenständig zu arbeiten.asyncioist sehr mächtig und ermöglicht dir eine Menge spannender Aufgaben. Dieasyncio-Bibliothek wurde für Netzwerksockets konzipiert.Eine großartige Bibliothek für asynchrones Socket-Programming vor
asyncioist das Twisted-Framework. Ein weiteres spannendes Projekt ist Dask, eine flexible Bibliothek für paralleles Rechnen in der Analytik. Stöbere in der Dokumentation zumasyncio-Modul und lass deinen Ideen freien Lauf!