Grundlagen und Anwendung der Python async/await-Syntax

Die async/await-Syntax von Python spielt eine wichtige Rolle bei der einfachen Darstellung von asynchronen Prozessen, insbesondere bei I/O-gebundenen Aufgaben und Anwendungen, die viele Anfragen verarbeiten. In diesem Artikel werden die grundlegenden Konzepte dieser Syntax sowie praktische Anwendungen und Beispiele verständlich erklärt. Lernen Sie die Grundlagen der asynchronen Programmierung und vertiefen Sie Ihr Verständnis durch konkrete Code-Beispiele.

Inhaltsverzeichnis

Grundkonzepte der async/await-Syntax


Die async/await-Syntax von Python sind Schlüsselwörter, die verwendet werden, um asynchrone Programmierung einfach umzusetzen. Mit ihrer Hilfe können langwierige Operationen (wie I/O-Operationen) effizient verarbeitet werden, wodurch die Reaktionsfähigkeit des Programms verbessert wird.

Was ist asynchrone Programmierung?


Asynchrone Programmierung ist eine Technik, bei der das Programm während des Wartens auf eine Aufgabe gleichzeitig andere Aufgaben ausführen kann. Während bei der synchronen Verarbeitung Aufgaben nacheinander ausgeführt werden, scheint bei der asynchronen Verarbeitung, dass mehrere Aufgaben „gleichzeitig“ ausgeführt werden.

Die Rolle von async und await

  • async: Wird verwendet, um eine Funktion als asynchron zu definieren. Diese Funktion wird als Koroutine bezeichnet und kann mit await andere asynchrone Prozesse aufrufen.
  • await: Wird verwendet, um auf das Ergebnis eines asynchronen Prozesses zu warten. Während des Wartens mit await können andere Aufgaben ausgeführt werden, was die Effizienz des gesamten Programms verbessert.

Ein einfaches Beispiel


Hier ist ein einfaches Beispiel für die Verwendung von async/await:

import asyncio

async def say_hello():
    print("Hello")
    await asyncio.sleep(1)  # 1 Sekunde warten
    print("World")

# Ausführung der asynchronen Funktion
asyncio.run(say_hello())

Dieser Code gibt „Hello“ aus, wartet dann 1 Sekunde und gibt „World“ aus. Während der Wartezeit mit await können andere asynchrone Aufgaben ausgeführt werden.

Merkmale von Koroutinen

  • Mit async definierte Funktionen können nicht direkt ausgeführt werden und müssen mit await oder asyncio.run() aufgerufen werden.
  • Um asynchrone Prozesse effizient zu nutzen, müssen Koroutinen und Aufgaben (die im nächsten Abschnitt behandelt werden) richtig kombiniert werden.

Übersicht und Rolle der asyncio-Bibliothek


Die asyncio-Bibliothek von Python, die Teil der Standardbibliothek ist, bietet ein Set von Tools zur effizienten Verwaltung asynchroner Prozesse. Damit können I/O-Operationen und die gleichzeitige Ausführung mehrerer Aufgaben einfach umgesetzt werden.

Die Rolle von asyncio

  • Verwaltung der Ereignisschleife: Sie spielt eine zentrale Rolle bei der Planung und Ausführung von Aufgaben.
  • Verwaltung von Koroutinen und Aufgaben: Sie registriert asynchrone Prozesse als Aufgaben und führt sie effizient aus.
  • Unterstützung für asynchrone I/O-Operationen: Sie führt Prozesse aus, die I/O-Wartezeiten wie Dateioperationen und Netzwerkkommunikation beinhalten.

Was ist eine Ereignisschleife?


Die Ereignisschleife ist eine Art Motor, der asynchrone Aufgaben der Reihe nach abarbeitet. In asyncio verwaltet diese Schleife asynchrone Funktionen und sorgt für eine effiziente Planung der Aufgaben.

import asyncio

async def example_task():
    print("Task started")
    await asyncio.sleep(1)
    print("Task finished")

async def main():
    # Ausführung der Aufgabe innerhalb der Ereignisschleife
    await example_task()

# Start der Ereignisschleife und Ausführung von main()
asyncio.run(main())

Wichtige asyncio-Funktionen und -Klassen

  • asyncio.run(): Startet die Ereignisschleife und führt eine asynchrone Funktion aus.
  • asyncio.create_task(): Registriert eine Koroutine als Aufgabe in der Ereignisschleife.
  • asyncio.sleep(): Wartet asynchron für eine bestimmte Zeit.
  • asyncio.gather(): Führt mehrere Aufgaben gleichzeitig aus und sammelt die Ergebnisse.
  • asyncio.Queue: Eine Warteschlange, die es ermöglicht, Daten effizient zwischen asynchronen Aufgaben auszutauschen.

Ein einfaches Anwendungsbeispiel


Hier ist ein Beispiel, bei dem mehrere Aufgaben gleichzeitig ausgeführt werden:

async def task1():
    print("Task 1 started")
    await asyncio.sleep(2)
    print("Task 1 finished")

async def task2():
    print("Task 2 started")
    await asyncio.sleep(1)
    print("Task 2 finished")

async def main():
    # Gleichzeitige Ausführung
    await asyncio.gather(task1(), task2())

asyncio.run(main())

In diesem Programm werden Task 1 und Task 2 gleichzeitig ausgeführt, wobei Task 2 zuerst abgeschlossen wird.

Vorteile von asyncio

  • Effiziente Verwaltung vieler Aufgaben.
  • Leistungssteigerung bei I/O-gebundenen Aufgaben.
  • Flexible Planung durch die Ereignisschleife.

Durch das Verständnis von asyncio können Sie das Potenzial der asynchronen Programmierung maximal ausschöpfen.

Unterschiede und Anwendung von Koroutinen und Aufgaben


Koroutinen und Aufgaben sind grundlegende Konzepte in der asynchronen Programmierung in Python. Durch das Verständnis ihrer Eigenschaften und Rollen können sie effizient eingesetzt werden, um asynchrone Prozesse umzusetzen.

Was ist eine Koroutine?


Eine Koroutine ist eine besondere Art von Funktion, die als asynchrone Funktion definiert wird. Sie wird mit async def definiert und kann andere asynchrone Prozesse mit await ausführen. Eine Koroutine kann während der Ausführung gestoppt und später wieder fortgesetzt werden.

Beispiel: Definition und Verwendung einer Koroutine

import asyncio

async def my_coroutine():
    print("Start coroutine")
    await asyncio.sleep(1)
    print("End coroutine")

# Ausführung der Koroutine
asyncio.run(my_coroutine())

Was ist eine Aufgabe?


Eine Aufgabe ist eine Koroutine, die für die Ausführung in der Ereignisschleife verpackt wird. Sie wird mit asyncio.create_task() erstellt und nach der Registrierung in der Ereignisschleife parallel ausgeführt.

Beispiel: Erstellen und Ausführen einer Aufgabe

import asyncio

async def my_coroutine(number):
    print(f"Coroutine {number} started")
    await asyncio.sleep(1)
    print(f"Coroutine {number} finished")

async def main():
    # Erstellen und gleichzeitige Ausführung mehrerer Aufgaben
    task1 = asyncio.create_task(my_coroutine(1))
    task2 = asyncio.create_task(my_coroutine(2))

    # Warten auf den Abschluss der Aufgaben
    await task1
    await task2

asyncio.run(main())

In diesem Beispiel starten Task 1 und Task 2 gleichzeitig, und ihre Verarbeitung erfolgt parallel.

Unterschiede zwischen Koroutinen und Aufgaben

MerkmalKoroutineAufgabe
Definierungasync defasyncio.create_task()
Ausführungawait oder asyncio.run()Wird automatisch in der Ereignisschleife ausgeführt
Gleichzeitige AusführungSchreibt eine einzelne asynchrone AufgabeErmöglicht parallele Ausführung mehrerer asynchroner Aufgaben

Tipps zur Verwendung

  • Koroutinen werden verwendet, wenn einfache asynchrone Aufgaben geschrieben werden sollen.
  • Aufgaben werden genutzt, wenn mehrere asynchrone Aufgaben parallel ausgeführt werden sollen.

Anwendungsbeispiel: Parallele Verarbeitung mit Aufgaben


Hier ein Beispiel, wie mehrere asynchrone Funktionen gleichzeitig ausgeführt werden:

import asyncio

async def fetch_data(url):
    print(f"Fetching data from {url}")
    await asyncio.sleep(2)  # Simulierte Netzwerkwartezeit
    print(f"Finished fetching data from {url}")

async def main():
    urls = ["https://example.com", "https://example.org", "https://example.net"]

    # Erstellen mehrerer Aufgaben
    tasks = [asyncio.create_task(fetch_data(url)) for url in urls]

    # Warten auf den Abschluss aller Aufgaben
    await asyncio.gather(*tasks)

asyncio.run(main())

In diesem Programm werden mit List Comprehension mehrere Aufgaben erzeugt, die parallel ausgeführt werden.

Wichtige Hinweise

  • Die Ausführungsreihenfolge von Aufgaben ist nicht garantiert, daher ist sie nicht für abhängige Prozesse geeignet.
  • Aufgaben können nur innerhalb der Ereignisschleife verwendet werden, nicht außerhalb der Schleife.

Durch das korrekte Verständnis der Unterschiede zwischen Koroutinen und Aufgaben können Sie asynchrone Programme effizient gestalten und optimieren.

Vorteile und Grenzen der asynchronen Verarbeitung


Asynchrone Verarbeitung ist besonders bei Anwendungen, die viele I/O-Operationen ausführen, ein wertvolles Werkzeug zur Leistungssteigerung, jedoch ist sie nicht überall einsetzbar. In diesem Abschnitt werden die Vorteile und Grenzen der asynchronen Verarbeitung erläutert, damit Sie sie gezielt einsetzen können.

Vorteile der asynchronen Verarbeitung

1. Geschwindigkeit und Effizienz

  • Nutzung von Ressourcen während der I/O-Wartezeiten: Während bei synchroner Verarbeitung das Programm während der I/O-Wartezeit stoppt, können bei asynchroner Verarbeitung andere Aufgaben ausgeführt werden, sodass Ressourcen effizienter genutzt werden.
  • Hohe Durchsatzleistung: Ideal für Server, die viele Anfragen gleichzeitig verarbeiten, oder Clients, die zahlreiche Netzwerkoperationen parallel durchführen.

2. Verbesserung der Reaktionsfähigkeit

  • Verbesserung der Benutzererfahrung: Asynchrone Verarbeitung ermöglicht es, Hintergrundaufgaben auszuführen, ohne die Benutzeroberfläche zu blockieren, wodurch die Reaktionsfähigkeit verbessert wird.
  • Reduzierung der Wartezeiten: Durch die Verwendung asynchroner I/O können andere Prozesse parallel ablaufen, wodurch die Gesamtladezeit verkürzt wird.

3. Flexibilität und Skalierbarkeit

  • Skalierbare Architektur: Asynchrone Programme verbrauchen keine übermäßigen Threads oder Prozesse und nutzen die Systemressourcen effizient.
  • Multitasking: Asynchrone Aufgaben können effizient zwischen den Aufgaben wechseln, sodass das System auch bei hoher Last stabil bleibt.

Grenzen der asynchronen Verarbeitung

1. Komplexität des Programms


Asynchrone Verarbeitung kann schwieriger zu verstehen und zu debuggen sein als synchrone Prozesse. Besonders in folgenden Bereichen können Probleme auftreten:

  • Rennbedingungen: Wenn mehrere Aufgaben auf dieselbe Ressource zugreifen, kann es schwierig sein, die Datenintegrität zu wahren.
  • Callback-Hölle: Bei komplexen Abhängigkeiten in asynchronen Prozessen kann der Code schwer lesbar werden.

2. Ineffizienz bei CPU-gebundenen Aufgaben


Asynchrone Verarbeitung ist hauptsächlich für I/O-gebundene Aufgaben optimiert. Bei rechenintensiven CPU-gebundenen Aufgaben kann es aufgrund von Einschränkungen wie dem GIL (Global Interpreter Lock) zu keinen Leistungssteigerungen kommen.

3. Notwendigkeit einer geeigneten Architektur


Um asynchrone Programme effektiv umzusetzen, ist eine angemessene Architektur und die Wahl der richtigen Bibliotheken erforderlich. Schlechte Designentscheidungen können zu Problemen führen:

  • Deadlocks: Aufgaben, die aufeinander warten und somit in einem Stillstand enden.
  • Fehlerhafte Planung: Ineffiziente Planung kann dazu führen, dass die Ausführung länger dauert als erwartet.

Tipps zur Nutzung der asynchronen Verarbeitung

1. Gezielter Einsatz

  • Für I/O-gebundene Aufgaben verwenden: Ideal für Datenbankoperationen, Netzwerkkommunikation, Dateioperationen und ähnliche Aufgaben.
  • CPU-gebundene Aufgaben mit Threads oder Prozessen behandeln: Kombinieren Sie asynchrone und parallele Verarbeitungstechniken.

2. Verwendung hochwertiger Tools und Bibliotheken

  • asyncio: Ein grundlegendes Werkzeug in der Standardbibliothek zur Verwaltung asynchroner Prozesse.
  • aiohttp: Eine Bibliothek für asynchrone HTTP-Kommunikation.
  • Quart und FastAPI: Asynchron unterstützte Web-Frameworks.

3. Umfassendes Debugging und Monitoring

  • Verwenden Sie Logs, um das Verhalten zwischen Aufgaben zu überwachen und beim Debuggen zu unterstützen.
  • Aktivieren Sie den Debugging-Modus von asyncio, um detaillierte Fehlermeldungen zu erhalten.

Die asynchrone Verarbeitung kann die Leistung von Anwendungen erheblich steigern, wenn sie richtig entworfen und eingesetzt wird. Gleichzeitig ist es wichtig, ihre Grenzen zu verstehen und eine geeignete Architektur zu wählen.

Erstellen von asynchronen Funktionen in der Praxis


Um asynchrone Prozesse in Python umzusetzen, kombinieren wir async und await, um asynchrone Funktionen zu definieren und auszuführen. In diesem Abschnitt lernen wir, wie man asynchrone Funktionen erstellt und die grundlegenden Abläufe der asynchronen Verarbeitung umsetzt.

Grundstruktur einer asynchronen Funktion


Eine asynchrone Funktion wird mit async def definiert. Innerhalb dieser Funktion verwenden wir await, um andere asynchrone Prozesse aufzurufen.

Beispiel einer einfachen asynchronen Funktion

import asyncio

async def greet():
    print("Hello,")
    await asyncio.sleep(1)  # Asynchrone 1 Sekunde warten
    print("World!")

# Asynchrone Funktion ausführen
asyncio.run(greet())

In diesem Beispiel wartet await asyncio.sleep(1) asynchron für 1 Sekunde. Während dieser Wartezeit können andere Aufgaben fortgesetzt werden.

Verknüpfung asynchroner Funktionen


Es ist auch möglich, mehrere asynchrone Funktionen zu verknüpfen und Aufgaben miteinander zu koordinieren.

Beispiel zur Verknüpfung asynchroner Funktionen

async def task1():
    print("Task 1 started")
    await asyncio.sleep(2)
    print("Task 1 finished")

async def task2():
    print("Task 2 started")
    await asyncio.sleep(1)
    print("Task 2 finished")

async def main():
    # Asynchrone Funktionen nacheinander ausführen
    await task1()
    await task2()

asyncio.run(main())

Hier wird die Funktion main als asynchrone Funktion definiert, die die asynchronen Funktionen task1 und task2 der Reihe nach ausführt.

Asynchrone Funktionen und parallele Verarbeitung


Um asynchrone Funktionen parallel auszuführen, verwenden wir asyncio.create_task. Dadurch können mehrere asynchrone Aufgaben gleichzeitig ausgeführt werden.

Beispiel zur parallelen Verarbeitung

async def task1():
    print("Task 1 started")
    await asyncio.sleep(2)
    print("Task 1 finished")

async def task2():
    print("Task 2 started")
    await asyncio.sleep(1)
    print("Task 2 finished")

async def main():
    # Erstellen von Aufgaben für parallele Ausführung
    task1_coroutine = asyncio.create_task(task1())
    task2_coroutine = asyncio.create_task(task2())

    # Warten auf den Abschluss beider Aufgaben
    await task1_coroutine
    await task2_coroutine

asyncio.run(main())

In diesem Beispiel werden task1 und task2 parallel ausgeführt. Task 2 wird nach 1 Sekunde abgeschlossen, danach endet Task 1.

Anwendungsbeispiel: Ein einfacher asynchroner Zähler


Hier ist ein Beispiel, bei dem mehrere Zähler gleichzeitig laufen. Dies wird mit asynchronen Funktionen erreicht.

async def count(number):
    for i in range(1, 4):
        print(f"Counter {number}: {i}")
        await asyncio.sleep(1)  # Asynchrone 1 Sekunde warten

async def main():
    # Mehrere Zähler parallel ausführen
    await asyncio.gather(count(1), count(2), count(3))

asyncio.run(main())

Ausgabe

Counter 1: 1
Counter 2: 1
Counter 3: 1
Counter 1: 2
Counter 2: 2
Counter 3: 2
Counter 1: 3
Counter 2: 3
Counter 3: 3

Durch die Verwendung asynchroner Verarbeitung wird deutlich, dass jeder Zähler unabhängig voneinander läuft.

Wichtige Punkte und Hinweise

  • Asynchrone Verarbeitung reduziert den Ressourcenverbrauch und ermöglicht eine effiziente Verwaltung von Aufgaben.
  • Verwenden Sie asyncio.gather oder asyncio.create_task, je nach Bedarf.
  • Führen Sie asynchrone Funktionen mit asyncio.run oder der Ereignisschleife aus.

Durch das Üben mit grundlegenden asynchronen Funktionen können Sie Ihre Fähigkeiten in der asynchronen Programmierung verbessern.

Methoden zur Umsetzung der Parallelverarbeitung: Verwendung von gather und wait


In der asynchronen Verarbeitung mit Python werden asyncio.gather und asyncio.wait verwendet, um mehrere Aufgaben effizient parallel auszuführen. Durch das Verständnis ihrer Merkmale und Anwendungsmöglichkeiten können flexiblere asynchrone Programme erstellt werden.

Überblick und Anwendungsbeispiel von asyncio.gather


asyncio.gather führt mehrere asynchrone Aufgaben zusammen aus und wartet, bis alle Aufgaben abgeschlossen sind. Nach Abschluss gibt es die Ergebnisse jeder Aufgabe als Liste zurück.

Beispiel

import asyncio

async def task1():
    await asyncio.sleep(1)
    return "Task 1 complete"

async def task2():
    await asyncio.sleep(2)
    return "Task 2 complete"

async def main():
    results = await asyncio.gather(task1(), task2())
    print(results)

asyncio.run(main())

Ergebnis der Ausführung

['Task 1 complete', 'Task 2 complete']

Merkmale

  • Wartet auf den Abschluss der parallel ausgeführten Aufgaben und gibt die Ergebnisse als Liste zurück.
  • Im Falle einer Ausnahme stoppt gather alle Aufgaben und propagiert die Ausnahme an den Aufrufer.

Überblick und Anwendungsbeispiel von asyncio.wait


asyncio.wait führt mehrere Aufgaben parallel aus und gibt ein Set von abgeschlossenen und ausstehenden Aufgaben zurück.

Beispiel

import asyncio

async def task1():
    await asyncio.sleep(1)
    print("Task 1 complete")

async def task2():
    await asyncio.sleep(2)
    print("Task 2 complete")

async def main():
    tasks = [task1(), task2()]
    done, pending = await asyncio.wait(tasks)
    print(f"Done tasks: {len(done)}, Pending tasks: {len(pending)}")

asyncio.run(main())

Ergebnis der Ausführung

Task 1 complete
Task 2 complete
Done tasks: 2, Pending tasks: 0

Merkmale

  • Ermöglicht es, den Status der Aufgaben (abgeschlossen oder ausstehend) detailliert zu überprüfen.
  • Auch wenn eine Aufgabe frühzeitig abgeschlossen wird, können ausstehende Aufgaben weiter verarbeitet werden.
  • Mit der return_when-Option von asyncio.wait können Sie das Ende der Aufgaben unter bestimmten Bedingungen steuern.

Beispiel für die return_when-Option

done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
  • FIRST_COMPLETED: Gibt zurück, wenn die erste Aufgabe abgeschlossen ist.
  • FIRST_EXCEPTION: Gibt zurück, wenn die erste Ausnahme auftritt.
  • ALL_COMPLETED: Wartet, bis alle Aufgaben abgeschlossen sind (Standard).

Unterschiedliche Verwendung von gather und wait

  • Wenn Sie die Ergebnisse gebündelt erhalten möchten: Verwenden Sie asyncio.gather.
  • Wenn Sie den Status jeder Aufgabe separat verwalten möchten: Verwenden Sie asyncio.wait.
  • Wenn Sie Aufgaben vorzeitig beenden oder Ausnahmen behandeln möchten: asyncio.wait ist geeignet.

Anwendungsbeispiel: Parallelabruf von APIs


Das folgende Beispiel zeigt, wie mehrere APIs parallel abgerufen und die Antworten erhalten werden:

import asyncio

async def fetch_data(api_name, delay):
    print(f"Fetching from {api_name}...")
    await asyncio.sleep(delay)  # Simulierte Wartezeit
    return f"Data from {api_name}"

async def main():
    apis = [("API_1", 2), ("API_2", 1), ("API_3", 3)]
    tasks = [fetch_data(api, delay) for api, delay in apis]

    # Parallelverarbeitung mit gather und Ergebnissammlung
    results = await asyncio.gather(*tasks)
    for result in results:
        print(result)

asyncio.run(main())

Ergebnis der Ausführung

Fetching from API_1...
Fetching from API_2...
Fetching from API_3...
Data from API_2
Data from API_1
Data from API_3

Wichtige Hinweise

  • Ausnahmebehandlung: Wenn bei parallelen Aufgaben Ausnahmen auftreten, müssen diese korrekt abgefangen und behandelt werden. Nutzen Sie try/except.
  • Aufgabenstornierung : Wenn Aufgaben nicht mehr benötigt werden, verwenden Sie task.cancel() zur Stornierung.
  • Achten Sie auf Deadlocks: Es ist wichtig, eine Architektur zu entwerfen, die gegenseitige Wartebedingungen vermeidet.

Durch den effektiven Einsatz von asyncio.gather und asyncio.wait können die Flexibilität und Effizienz der asynchronen Verarbeitung maximiert werden.

Beispiel für asynchrones I/O: Datei- und Netzwerkoperationen


Asynchrones I/O ist eine Methode zur Effizienzsteigerung bei Operationen, die auf Wartezeiten angewiesen sind, wie Dateioperationen und Netzwerkkommunikation. Durch die Nutzung von asyncio können solche asynchronen I/O-Operationen einfach implementiert werden. In diesem Abschnitt werden die grundlegenden Anwendungen von asynchronem I/O anhand konkreter Beispiele erklärt.

Asynchrone Dateioperationen


Für asynchrone Dateioperationen wird die Bibliothek aiofiles verwendet. Diese Bibliothek erweitert die Standardbibliothek, sodass Dateioperationen asynchron durchgeführt werden können.

Beispiel: Asynchrone Dateioperationen

import aiofiles
import asyncio

async def read_file(filepath):
    async with aiofiles.open(filepath, mode='r') as file:
        contents = await file.read()
        print(f"Contents of {filepath}:")
        print(contents)

async def write_file(filepath, data):
    async with aiofiles.open(filepath, mode='w') as file:
        await file.write(data)
        print(f"Data written to {filepath}")

async def main():
    filepath = 'example.txt'
    await write_file(filepath, "Hello, Async File IO!")
    await read_file(filepath)

asyncio.run(main())

Wichtige Punkte

  • Mit aiofiles.open können Sie Dateien asynchron bearbeiten.
  • Verwenden Sie die async with-Syntax, um Dateien sicher zu handhaben.
  • Während Dateioperationen fortschreiten, können andere Aufgaben gleichzeitig durchgeführt werden.

Asynchrone Netzwerkoperationen


Für Netzwerkoperationen kann die Bibliothek aiohttp verwendet werden, um asynchrone HTTP-Anfragen zu stellen.

Beispiel: Asynchrone HTTP-Anfragen

import aiohttp
import asyncio

async def fetch_url(session, url):
    async with session.get(url) as response:
        print(f"Fetching {url}")
        content = await response.text()
        print(f"Content from {url}: {content[:100]}...")

async def main():
    urls = [
        "https://example.com",
        "https://example.org",
        "https://example.net"
    ]

    async with aiohttp.ClientSession() as session:
        tasks = [fetch_url(session, url) for url in urls]
        await asyncio.gather(*tasks)

asyncio.run(main())

Wichtige Punkte

  • Verwenden Sie aiohttp.ClientSession für asynchrone HTTP-Kommunikation.
  • Mit der async with-Syntax verwalten Sie die Sitzung und senden Anfragen sicher.
  • Durch parallele Anfragen mit asyncio.gather wird die Effizienz gesteigert.

Kombination von asynchronem Datei- und Netzwerk-I/O


Durch die Kombination von asynchronen Datei- und Netzwerkoperationen können Daten effizient gesammelt und gespeichert werden.

Beispiel: Speichern von heruntergeladenen Daten asynchron

import aiohttp
import aiofiles
import asyncio

async def fetch_and_save(session, url, filepath):
    async with session.get(url) as response:
        print(f"Fetching {url}")
        content = await response.text()

        async with aiofiles.open(filepath, mode='w') as file:
            await file.write(content)
            print(f"Content from {url} saved to {filepath}")

async def main():
    urls = [
        ("https://example.com", "example_com.txt"),
        ("https://example.org", "example_org.txt"),
        ("https://example.net", "example_net.txt")
    ]

    async with aiohttp.ClientSession() as session:
        tasks = [fetch_and_save(session, url, filepath) for url, filepath in urls]
        await asyncio.gather(*tasks)

asyncio.run(main())

Beispielergebnis

  • Der Inhalt von https://example.com wird in der Datei example_com.txt gespeichert.
  • Der Inhalt der anderen URLs wird ebenfalls in die entsprechenden Dateien gespeichert.

Wichtige Hinweise zum Asynchronen I/O

  1. Implementierung der Ausnahmebehandlung
    Bereiten Sie sich auf Netzwerkfehler und Datei-Schreibfehler vor, indem Sie geeignete Ausnahmebehandlung durchführen.
   try:
       # Asynchrone Aufgaben
   except Exception as e:
       print(f"An error occurred: {e}")
  1. Implementierung der Drosselung
    Bei der gleichzeitigen Ausführung vieler asynchroner Aufgaben kann dies die System- oder Serverlast erhöhen. Mit asyncio.Semaphore können Sie die Anzahl der gleichzeitig ausgeführten Aufgaben begrenzen.
   semaphore = asyncio.Semaphore(5)  # Maximale Anzahl paralleler Aufgaben

   async with semaphore:
       await some_async_task()
  1. Timeout-Implementierung
    Um Prozesse ohne Antwort zu verhindern, setzen Sie ein Timeout.
   try:
       await asyncio.wait_for(some_async_task(), timeout=10)
   except asyncio.TimeoutError:
       print("Task timed out")

Durch den richtigen Einsatz von asynchronem I/O können Sie die Effizienz und den Durchsatz Ihrer Anwendungen erheblich steigern.

Anwendungsbeispiel: Aufbau eines asynchronen Web-Crawlers


Mit asynchroner Verarbeitung können Sie einen schnellen und effizienten Web-Crawler erstellen. Mit asynchronem I/O können Sie viele Webseiten parallel abrufen und die Crawling-Geschwindigkeit maximieren. In diesem Abschnitt zeigen wir ein Beispiel für die Implementierung eines asynchronen Web-Crawlers mit Python.

Grundstruktur eines asynchronen Web-Crawlers


Ein asynchroner Web-Crawler besteht aus drei wichtigen Elementen:

  1. Verwaltung der URL-Liste: Effiziente Verwaltung der URLs, die gecrawlt werden sollen.
  2. Asynchrone HTTP-Kommunikation: Abrufen von Webseiten mit der asynchronen Bibliothek aiohttp.
  3. Speichern der Daten: Speichern der abgerufenen Daten mit asynchronen Dateioperationen.

Codebeispiel: Asynchroner Web-Crawler


Das folgende Beispiel zeigt den grundlegenden Aufbau eines asynchronen Web-Crawlers:

import aiohttp
import aiofiles
import asyncio
from bs4 import BeautifulSoup

async def fetch_page(session, url):
    try:
        async with session.get(url) as response:
            if response.status == 200:
                html = await response.text()
                print(f"Fetched {url}")
                return html
            else:
                print(f"Failed to fetch {url}: {response.status}")
                return None
    except Exception as e:
        print(f"Error fetching {url}: {e}")
        return None

async def parse_and_save(html, url, filepath):
    if html:
        soup = BeautifulSoup(html, 'html.parser')
        title = soup.title.string if soup.title else "No Title"
        async with aiofiles.open(filepath, mode='a') as file:
            await file.write(f"URL: {url}\nTitle: {title}\n\n")
        print(f"Saved data for {url}")

async def crawl(urls, output_file):
    async with aiohttp.ClientSession() as session:
        tasks = []
        for url in urls:
            tasks.append(process_url(session, url, output_file))
        await asyncio.gather(*tasks)

async def process_url(session, url, output_file):
    html = await fetch_page(session, url)
    await parse_and_save(html, url, output_file)

async def main():
    urls = [
        "https://example.com",
        "https://example.org",
        "https://example.net"
    ]
    output_file = "crawl_results.txt"

    # Initialisierung: Leeren der Ergebnisdatei
    async with aiofiles.open(output_file, mode='w') as file:
        await file.write("")

    await crawl(urls, output_file)

asyncio.run(main())

Erklärung der Codeausführung

  1. fetch_page-Funktion
    Führt eine asynchrone HTTP-Anfrage durch, um die HTML-Seite abzurufen. Überprüft den Statuscode und behandelt Fehler.
  2. parse_and_save-Funktion
    Verwendet BeautifulSoup, um das HTML zu parsen und den Titel der Seite zu extrahieren. Speichert diese Daten asynchron in einer Datei.
  3. crawl-Funktion
    Verarbeitet die URL-Liste und führt die URLs parallel aus. Verwendet asyncio.gather, um die Aufgaben zu bündeln.
  4. process_url-Funktion
    Kapselt die vollständige Verarbeitung einer URL mit fetch_page und parse_and_save.

Beispiel für die Ergebnisse der Ausführung


Die Datei crawl_results.txt wird die folgenden Daten enthalten:

URL: https://example.com
Title: Example Domain

URL: https://example.org
Title: Example Domain

URL: https://example.net
Title: Example Domain

Leistungsoptimierung

  • Begrenzung der parallelen Aufgaben
    Wenn viele URLs gecrawlt werden, begrenzen Sie die Anzahl paralleler Aufgaben, um die Serverlast zu verringern.
  semaphore = asyncio.Semaphore(10)

  async def limited_process_url(semaphore, session, url, output_file):
      async with semaphore:
          await process_url(session, url, output_file)
  • Hinzufügen einer Retry-Funktion
    Durch die Implementierung einer Logik zur Wiederholung von fehlgeschlagenen Anfragen können Sie die Zuverlässigkeit erhöhen.

Wichtige Hinweise

  1. Überprüfung der Legalität
    Wenn Sie einen Web-Crawler betreiben, stellen Sie sicher, dass Sie die robots.txt und die Nutzungsbedingungen der Zielwebseite einhalten.
  2. Ausnahmebehandlung
    Behandeln Sie Netzwerkfehler und HTML-Parsing-Fehler korrekt, um den Betrieb des Crawlers nicht zu stoppen.
  3. Timeout-Implementierung
    Setzen Sie ein Timeout für Anfragen, um endloses Warten zu vermeiden.
   async with session.get(url, timeout=10) as response:

Ein asynchroner Web-Crawler ermöglicht mit der richtigen Gestaltung und Kontrolle eine effiziente und skalierbare Datensammlung.

Zusammenfassung


In diesem Artikel haben wir die asynchrone Verarbeitung mit Python unter Verwendung der async/await-Syntax detailliert behandelt, von den Grundlagen bis hin zu fortgeschrittenen Anwendungen. Das Verständnis der asynchronen Verarbeitung ermöglicht es, I/O-intensive Aufgaben effizienter zu gestalten und die Leistung von Anwendungen zu steigern.

Besonders die Grundlagen der asyncio-Bibliothek, die parallele Verarbeitung mit gather und wait, konkrete Beispiele für asynchrones I/O und die Implementierung eines asynchronen Web-Crawlers haben uns praktische Fähigkeiten vermittelt.

Asynchrone Programmierung unterstützt den Aufbau effizienter und skalierbarer Systeme, erfordert jedoch sorgfältige Ausnahmebehandlung und rechtliche Überlegungen. Nutzen Sie diesen Artikel als Referenz, um Ihre Fähigkeiten in der asynchronen Verarbeitung zu erweitern und anzuwenden.

Inhaltsverzeichnis