Tiefes Eintauchen in Asyncio Coroutines, Event Loops und Async Await – Die Grundlagen entschlüsselt
Emily Parker
Product Engineer · Leapcell

Einleitung
In der heutigen vernetzten Welt stoßen Anwendungen häufig auf Szenarien, in denen sie mehrere Operationen durchführen müssen, die auf externe Ressourcen warten, wie z. B. Netzwerkanfragen, Datenbankabfragen oder Datei-E/A. Traditionell würde das Blockieren dieser Operationen zu einer ineffizienten Ressourcennutzung und einer schlechten Benutzererfahrung führen. Hier kommt die asynchrone Programmierung ins Spiel, ein Paradigmenwechsel, der es Programmen ermöglicht, eine Operation zu initiieren und dann zu einer anderen Aufgabe zu wechseln, während auf die Fertigstellung der ersten gewartet wird. Pythons asyncio
-Bibliothek bietet ein robustes und elegantes Framework für das Schreiben von nebenläufigem Code mit einem einzigen Thread, das Entwicklern die Erstellung hoch skalierbarer und reaktionsfreudiger Anwendungen ermöglicht. Dieser Artikel taucht tief in die grundlegenden Elemente von asyncio
ein: Coroutines, Event Loops und die async/await
-Syntax, entschlüsselt ihre inneren Abläufe und demonstriert, wie sie kooperatives Multitasking orchestrieren.
Kernkonzepte erklärt
Bevor wir uns mit den Mechanismen von asyncio
befassen, wollen wir ein klares Verständnis seiner grundlegenden Bausteine schaffen:
- Coroutines: Im Grunde sind Coroutines spezielle Funktionen, die angehalten und fortgesetzt werden können. Im Gegensatz zu regulären Funktionen, die nach dem Aufruf bis zum Ende ausgeführt werden, können Coroutines die Kontrolle an den Aufrufer
yield
en, was die Ausführung anderer Aufgaben ermöglicht, und dann genau dort weitermachen, wo sie aufgehört haben. In Python werden Coroutines mitasync def
definiert. - Event Loop: Die Event Loop ist der zentrale Orchestrator von
asyncio
. Sie überwacht kontinuierlich auf Ereignisse (z. B. E/A-Bereitschaft, Timer, abgeschlossene Aufgaben) und leitet sie an die entsprechenden Coroutines weiter. Sie fungiert als Single-Threaded-Scheduler, der den Ausführungsfluss aller asynchronen Aufgaben verwaltet. - Tasks: Eine Aufgabe ist eine Abstraktion über eine Coroutine, die sie in ein Future-ähnliches Objekt verpackt. Wenn eine Coroutine für die Ausführung auf der Event Loop geplant wird, wird sie zu einer Aufgabe. Aufgaben ermöglichen es der Event Loop, den Lebenszyklus von Coroutines zu verwalten, einschließlich deren Abbruch und Abschluss.
- Futures: Ein
Future
-Objekt repräsentiert das Endergebnis einer asynchronen Operation. Es ist ein Low-Level-Objekt, das „awaited“ werden kann, um sein Ergebnis oder eine Ausnahme zu erhalten. Tasks sind eine höherwertige Abstraktion, die auf Futures aufbaut. async
undawait
: Diese Schlüsselwörter sind synthetischer Zucker, der das Schreiben von Coroutines und die Interaktion mit asynchronen Operationen natürlicher macht.async
definiert eine Funktion als Coroutine, wodurch sie awaitable wird.await
pausiert die Ausführung der aktuellen Coroutine, bis die awaited „awaitable“ (eine andere Coroutine, einTask
oder einFuture
) abgeschlossen ist, und ermöglicht es der Event Loop, zu anderen Aufgaben zu wechseln.
Die inneren Abläufe von Asyncio
Die Leistung von asyncio
beruht auf der kooperativen Planung, die von der Event Loop orchestriert wird. Lassen Sie uns aufschlüsseln, wie diese Komponenten zusammenarbeiten:
Coroutines warten auf Operationen
Betrachten Sie eine typische synchrone Funktion, die Daten aus einem Netzwerk abruft:
import time def fetch_data_sync(url): print(f"Fetching data synchronously from {url}...") time.sleep(2) # Simulate network latency print(f"Finished fetching data from {url}.") return {"data": f"content from {url}"} # Synchronous execution start_time = time.time() data1 = fetch_data_sync("http://example.com/api/1") data2 = fetch_data_sync("http://example.com/api/2") end_time = time.time() print(f"Synchronous execution time: {end_time - start_time:.2f} seconds")
In diesem synchronen Beispiel wird fetch_data_sync("http://example.com/api/2")
erst gestartet, nachdem fetch_data_sync("http://example.com/api/1")
vollständig abgeschlossen ist, einschließlich seiner simulierten 2-Sekunden-Verzögerung.
Lassen Sie uns nun sehen, wie sich dies mit asyncio
unter Verwendung von async def
und await
ändert:
import asyncio import time async def fetch_data_async(url): print(f"Fetching data asynchronously from {url}...") await asyncio.sleep(2) # Non-blocking sleep, yields control print(f"Finished fetching data from {url}.") return {"data": f"content from {url}"} async def main(): start_time = time.time() # Schedule both coroutines to run concurrently task1 = asyncio.create_task(fetch_data_async("http://example.com/api/1")) task2 = asyncio.create_task(fetch_data_async("http://example.com/api/2")) # Await their completion data1 = await task1 data2 = await task2 end_time = time.time() print(f"Asynchronous execution time: {end_time - start_time:.2f} seconds") print(f"Data 1: {data1}") print(f"Data 2: {data2}") if __name__ == "__main__": asyncio.run(main())
In der asynchronen Version:
async def fetch_data_async(url):
deklariertfetch_data_async
als Coroutine.await asyncio.sleep(2)
ist der entscheidende Teil. Wenn die Ausführung diese Zeile erreicht, pausiert diefetch_data_async
-Coroutine und gibt die Kontrolle an die Event Loop zurück, anstatt 2 Sekunden lang zu blockieren.- Die Event Loop sucht dann nach anderen Aufgaben, die zur Ausführung bereit sind. In unserer
main
-Funktion haben wir zwei Aufgaben erstellt:task1
undtask2
. - Nachdem
task1
awaited wird, kann die Event Loop zutask2
wechseln, die dann ebenfalls ihre Ausführung startet und schließlichasyncio.sleep(2)
awaited. - Während beide Coroutines „schlafen“ (
awaiting
), überwacht die Event Loop Timer. Nach 2 Sekunden erhält sie ein Signal, dassasyncio.sleep(2)
fürtask1
abgeschlossen ist. Sie setzt danntask1
von dort fort, wo es unterbrochen wurde, und ermöchtigt es, „Finished fetching data...“ zu drucken und sein Ergebnis zurückzugeben. - Ebenso wird
task2
nach Abschluss seines Sleeps fortgesetzt. - Schließlich rufen
await task1
undawait task2
inmain
ihre jeweiligen Ergebnisse ab.
Der wichtigste Punkt ist, dass await
nicht das gesamte Programm blockiert, sondern nur die aktuelle Coroutine, wodurch die Event Loop andere Aufgaben gleichzeitig verarbeiten kann. Dies ist kooperatives Multitasking: Coroutines geben explizit die Kontrolle ab.
Die Rolle der Event Loop
Die Event Loop wird mithilfe einer Low-Level-Konstruktion (oft selectors
oder plattformspezifische Mechanismen wie epoll
unter Linux, kqueue
unter macOS oder IOCP
unter Windows) implementiert, um mehrere E/A-Operationen effizient gleichzeitig zu überwachen, ohne zu blockieren.
Wenn Sie asyncio.run(main())
aufrufen, geschieht normalerweise Folgendes:
- Eine Event Loop-Instanz wird erstellt (wenn im aktuellen Thread nicht bereits eine läuft).
- Die
main()
-Coroutine wird auf dieser Event Loop geplant. - Die Event Loop startet ihren Hauptausführungszyklus:
- Sie wählt eine Aufgabe aus, die zur Ausführung bereit ist (d. h. nicht gerade awaited).
- Sie führt diese Aufgabe aus, bis sie auf einen
await
-Ausdruck stößt. - Beim Erreichen von
await
wird die aktuelle Aufgabe unterbrochen und ihr Zustand gespeichert. - Die Event Loop prüft dann abgeschlossene E/A-Operationen, abgelaufene Timer oder andere eingeplante Ereignisse.
- Wenn eine awaited Operation (wie
asyncio.sleep
oder ein Netzwerk-Read) abgeschlossen ist, wird die entsprechende pausierte Aufgabe als zur Fortsetzung bereit markiert. - Die Event Loop wählt dann eine andere bereite Aufgabe aus und setzt den Zyklus fort.
- Dieser Zyklus wird fortgesetzt, bis alle geplanten Aufgaben abgeschlossen sind.
Praktische Anwendung mit aiohttp
Lassen Sie uns dies mit einem gängigen Anwendungsfall veranschaulichen: gleichzeitiges Ausführen mehrerer HTTP-Anfragen mit aiohttp
, einer asyncio
-kompatiblen HTTP-Client-Bibliothek.
import asyncio import aiohttp import time async def fetch_url(session, url): async with session.get(url) as response: return await response.text() async def main_http(): urls = [ "https://www.example.com", "https://www.google.com", "https://www.bing.com", "https://www.python.org" ] start_time = time.time() async with aiohttp.ClientSession() as session: tasks = [] for url in urls: task = asyncio.create_task(fetch_url(session, url)) tasks.append(task) # Gather all results concurrently responses = await asyncio.gather(*tasks) end_time = time.time() print(f"Fetched {len(urls)} URLs in {end_time - start_time:.2f} seconds") # print first 100 chars of each response for i, res in enumerate(responses): print(f"URL {urls[i]} content snippet: {res[:100]}...") if __name__ == "__main__": asyncio.run(main_http())
In diesem Beispiel:
aiohttp.ClientSession()
ist ein asynchroner Kontextmanager, der eine ordnungsgemäße Ressourcenverwaltung gewährleistet.- Für jede
url
wirdfetch_url
als Coroutine aufgerufen. Die Aufrufeawait session.get(url)
undawait response.text()
sind nicht blockierend. Wennsession.get
eine Netzwerkanfrage initiiert, gibt es die Kontrolle ab und ermöglicht der Event Loop, die nächste Anfrage zu starten. asyncio.gather(*tasks)
ist ein leistungsstarkes Hilfsprogramm, das mehrere awaitables (unseretasks
) entgegennimmt und sie gleichzeitig ausführt. Es wartet, bis alle abgeschlossen sind, und gibt ihre Ergebnisse in der Reihenfolge zurück, in der die Aufgaben übergeben wurden.
Dies zeigt, wie asyncio
uns ermöglicht, E/A-Operationen zu überlappen und die Gesamtausführungszeit im Vergleich zum sequenziellen Abrufen jeder URL erheblich zu verkürzen.
Fazit
asyncio
bietet mit seinen Kernkonzepten von Coroutines, der Event Loop und der async/await
-Syntax einen leistungsstarken und intuitiven Weg, effiziente, nebenläufige Python-Anwendungen zu schreiben. Indem wir verstehen, wie Coroutines kooperativ die Kontrolle abgeben und wie die Event Loop ihre Ausführung orchestriert, können Entwickler das volle Potenzial der Single-Threaded-Asynchronprogrammierung nutzen, um hochgradig reaktionsfähige und skalierbare Systeme zu erstellen. asyncio
ist nicht nur eine Bibliothek; es ist ein grundlegender Wandel hin zu einem effizienteren und moderneren Ansatz für Nebenläufigkeit in Python.