Beginnen Sie mit Async in Python

Asynchrone Programmierung, kurz Async , ist eine Funktion vieler moderner Sprachen, die es einem Programm ermöglicht, mehrere Operationen zu jonglieren, ohne zu warten oder an einer von ihnen hängen zu bleiben. Dies ist eine intelligente Methode, um Aufgaben wie Netzwerk- oder Datei-E / A effizient zu erledigen, wobei die meiste Zeit des Programms damit verbracht wird, auf den Abschluss einer Aufgabe zu warten.

Stellen Sie sich eine Web-Scraping-Anwendung vor, die 100 Netzwerkverbindungen öffnet. Sie können eine Verbindung öffnen, auf die Ergebnisse warten, dann die nächste öffnen und auf die Ergebnisse warten und so weiter. Die meiste Zeit, die das Programm ausführt, wird auf eine Netzwerkantwort gewartet, ohne die eigentliche Arbeit zu erledigen.

Async bietet Ihnen eine effizientere Methode: Öffnen Sie alle 100 Verbindungen gleichzeitig und wechseln Sie dann zwischen den einzelnen aktiven Verbindungen, wenn sie Ergebnisse zurückgeben. Wenn eine Verbindung keine Ergebnisse zurückgibt, wechseln Sie zur nächsten usw., bis alle Verbindungen ihre Daten zurückgegeben haben.

Asynchrone Syntax ist jetzt eine Standardfunktion in Python, aber langjährige Pythonisten, die es gewohnt sind, jeweils eine Sache zu tun, haben möglicherweise Probleme, ihre Köpfe darum zu wickeln. In diesem Artikel erfahren Sie, wie die asynchrone Programmierung in Python funktioniert und wie sie verwendet wird.

Beachten Sie, dass Sie, wenn Sie Async in Python verwenden möchten, am besten Python 3.7 oder Python 3.8 (die neueste Version zum Zeitpunkt dieses Schreibens) verwenden. Wir werden die asynchrone Syntax und die Hilfsfunktionen von Python verwenden, wie sie in diesen Versionen der Sprache definiert sind.

Wann wird die asynchrone Programmierung verwendet?

Im Allgemeinen ist die beste Zeit für die Verwendung von Async, wenn Sie versuchen, Arbeiten mit den folgenden Merkmalen auszuführen:

  • Die Arbeit dauert lange.
  • Die Verzögerung umfasst das Warten auf E / A-Vorgänge (Festplatte oder Netzwerk) und nicht die Berechnung.
  • Die Arbeit umfasst viele E / A-Vorgänge gleichzeitig oder einen oder mehrere E / A-Vorgänge, wenn Sie auch versuchen, andere Aufgaben zu erledigen.

Mit Async können Sie mehrere Aufgaben parallel einrichten und effizient durchlaufen, ohne den Rest Ihrer Anwendung zu blockieren.

Einige Beispiele für Aufgaben, die mit Async gut funktionieren:

  • Web Scraping, wie oben beschrieben.
  • Netzwerkdienste (z. B. ein Webserver oder ein Framework).
  • Programme, die Ergebnisse aus mehreren Quellen koordinieren, deren Rückgabe lange dauert (z. B. gleichzeitige Datenbankabfragen).

Es ist wichtig zu beachten, dass sich die asynchrone Programmierung von Multithreading oder Multiprocessing unterscheidet. Asynchrone Vorgänge werden alle im selben Thread ausgeführt, geben sich jedoch nach Bedarf gegenseitig nach, wodurch Async für viele Arten von Aufgaben effizienter als Threading oder Multiprocessing wird. (Mehr dazu weiter unten.)

Python asyncawaitundasyncio

Python hat kürzlich zwei Schlüsselwörter hinzugefügt asyncund awaitzum Erstellen von asynchronen Vorgängen. Betrachten Sie dieses Skript:

def get_server_status (server_addr) # Eine möglicherweise lange laufende Operation ... return server_status def server_ops () results = [] results.append (get_server_status ('addr1.server') results.append (get_server_status ('addr2.server') return Ergebnisse 

Eine asynchrone Version desselben Skripts - nicht funktionsfähig, gerade genug, um uns eine Vorstellung davon zu geben, wie die Syntax funktioniert - könnte so aussehen.

async def get_server_status (server_addr) # Eine möglicherweise lange laufende Operation ... return server_status async def server_ops () results = [] results.append (warte auf get_server_status ('addr1.server') results.append (warte auf get_server_status ('addr2). server ') Ergebnisse zurückgeben 

Funktionen, denen das asyncSchlüsselwort vorangestellt ist, werden zu asynchronen Funktionen, auch als Coroutinen bezeichnet . Coroutinen verhalten sich anders als reguläre Funktionen:

  • Coroutinen können ein anderes Schlüsselwort verwenden, awaitmit dem eine Coroutine auf Ergebnisse einer anderen Coroutine warten kann, ohne sie zu blockieren. Bis die Ergebnisse von der awaited-Coroutine zurückkommen , wechselt Python frei zwischen anderen laufenden Coroutinen.
  • Coroutinen können nur von anderen asyncFunktionen aufgerufen werden. Wenn Sie den Hauptteil des Skripts ausführen server_ops()oder unverändert ausführen get_server_status(), werden die Ergebnisse nicht angezeigt. Sie erhalten ein Python-Coroutine-Objekt, das nicht direkt verwendet werden kann.

Wenn wir also keine asyncFunktionen von nicht asynchronen Funktionen aufrufen und keine asyncFunktionen direkt ausführen können, wie verwenden wir sie? Antwort: Verwenden Sie die asyncioBibliothek, die asyncden Rest von Python verbindet.

Python asyncawaitund asyncioBeispiel

Hier ist ein Beispiel (wieder nicht funktional, aber illustrativ), wie man eine Web-Scraping-Anwendung mit asyncund schreiben könnte asyncio. Dieses Skript verwendet eine Liste von URLs und verwendet mehrere Instanzen einer asyncFunktion aus einer externen Bibliothek ( read_from_site_async()), um sie herunterzuladen und die Ergebnisse zu aggregieren.

Asyncio aus web_scraping_library importieren read_from_site_async importieren async def main (url_list): Rückkehr warten asyncio.gather (* [read_from_site_async (_) für _ in url_list]) urls = ['//site1.com','//othersite.com', '//newsite.com'] results = asyncio.run (main (urls)) print (Ergebnisse) 

Im obigen Beispiel verwenden wir zwei allgemeine asyncioFunktionen:

  • asyncio.run()wird verwendet, um eine asyncFunktion aus dem nicht asynchronen Teil unseres Codes zu starten und damit alle asynchronen Aktivitäten des Programms zu starten. (So ​​laufen wir main().)
  • asyncio.gather()Nimmt eine oder mehrere asynchron dekorierte Funktionen (in diesem Fall mehrere Instanzen read_from_site_async()aus unserer hypothetischen Web-Scraping-Bibliothek), führt sie alle aus und wartet darauf, dass alle Ergebnisse eingehen.

Die Idee hier ist, dass wir den Lesevorgang für alle Sites gleichzeitig starten und dann die Ergebnisse sammeln, sobald sie eintreffen (daher asyncio.gather()). Wir warten nicht, bis eine Operation abgeschlossen ist, bevor wir zur nächsten übergehen.

Komponenten von Python-Async-Apps

Wir haben bereits erwähnt, wie asynchrone Python-Apps Coroutinen als Hauptbestandteil verwenden und dabei auf die asyncioBibliothek zurückgreifen. Einige andere Elemente sind ebenfalls der Schlüssel zu asynchronen Anwendungen in Python:

Ereignisschleifen

Die asyncioBibliothek erstellt und verwaltet Ereignisschleifen , die Mechanismen, mit denen Coroutinen ausgeführt werden, bis sie abgeschlossen sind. In einem Python-Prozess sollte jeweils nur eine Ereignisschleife ausgeführt werden, um dem Programmierer den Zugriff zu erleichtern.

Aufgaben

Wenn Sie eine Coroutine zur Verarbeitung an eine Ereignisschleife senden, können Sie ein TaskObjekt zurückerhalten , mit dem Sie das Verhalten der Coroutine von außerhalb der Ereignisschleife steuern können. Wenn Sie beispielsweise die laufende Aufgabe abbrechen müssen, können Sie dies tun, indem Sie die .cancel()Methode der Aufgabe aufrufen .

Here is a slightly different version of the site-scraper script that shows the event loop and tasks at work:

import asyncio from web_scraping_library import read_from_site_async tasks = [] async def main(url_list): for n in url_list: tasks.append(asyncio.create_task(read_from_site_async(n))) print (tasks) return await asyncio.gather(*tasks) urls = ['//site1.com','//othersite.com','//newsite.com'] loop = asyncio.get_event_loop() results = loop.run_until_complete(main(urls)) print (results) 

This script uses the event loop and task objects more explicitly.

  • The .get_event_loop() method provides us with an object that lets us control the event loop directly, by submitting async functions to it programmatically via .run_until_complete(). In the previous script, we could only run a single top-level async function, using asyncio.run(). By the way, .run_until_complete() does exactly what it says: It runs all of the supplied tasks until they’re done, then returns their results in a single batch.
  • The .create_task() method takes a function to run, including its parameters, and gives us back a Task object to run it. Here we submit each URL as a separate Task to the event loop, and store the Task objects in a list. Note that we can only do this inside the event loop—that is, inside an async function.

How much control you need over the event loop and its tasks will depend on how complex the application is that you’re building. If you just want to submit a set of fixed jobs to run concurrently, as with our web scraper, you won’t need a whole lot of control—just enough to launch jobs and gather the results. 

By contrast, if you’re creating a full-blown web framework, you’ll want far more control over the behavior of the coroutines and the event loop. For instance, you may need to shut down the event loop gracefully in the event of an application crash, or run tasks in a threadsafe manner if you’re calling the event loop from another thread.

Async vs. threading vs. multiprocessing

At this point you may be wondering, why use async instead of threads or multiprocessing, both of which have been long available in Python?

First, there is a key difference between async and threads or multiprocessing, even apart from how those things are implemented in Python. Async is about concurrency, while threads and multiprocessing are about parallelism. Concurrency involves dividing time efficiently among multiple tasks at once—e.g., checking your email while waiting for a register at the grocery store. Parallelism involves multiple agents processing multiple tasks side by side—e.g., having five separate registers open at the grocery store.

Most of the time, async is a good substitute for threading as threading is implemented in Python. This is because Python doesn’t use OS threads but its own cooperative threads, where only one thread is ever running at a time in the interpreter. In comparison to cooperative threads, async provides some key advantages:

  • Async functions are far more lightweight than threads. Tens of thousands of asynchronous operations running at once will have far less overhead than tens of thousands of threads.
  • The structure of async code makes it easier to reason about where tasks pick up and leave off. This means data races and thread safety are less of an issue. Because all tasks in the async event loop run in a single thread, it’s easier for Python (and the developer) to serialize how they access objects in memory.
  • Async operations can be cancelled and manipulated more readily than threads. The Task object we get back from asyncio.create_task() provides us with a handy way to do this.

Multiprocessing in Python, on the other hand, is best for jobs that are heavily CPU-bound rather than I/O-bound. Async actually works hand-in-hand with multiprocessing, as you can use asyncio.run_in_executor() to delegate CPU-intensive jobs to a process pool from a central process, without blocking that central process.

Next steps with Python async

The best first thing to do is build a few, simple async apps of your own. Good examples abound now that asynchronous programming in Python has undergone a few versions and had a couple of years to settle down and become more widely used. The official documentation for asyncio is worth reading over to see what it offers, even if you don’t plan to make use of all of its functions.

Sie können auch die wachsende Anzahl von asynchronen Bibliotheken und Middleware untersuchen, von denen viele asynchrone, nicht blockierende Versionen von Datenbankkonnektoren, Netzwerkprotokollen und dergleichen bereitstellen. Das aio-libsRepository verfügt über einige wichtige Elemente, z. B. die aiohittpBibliothek für den Webzugriff. Es lohnt sich auch, den Python-Paketindex nach Bibliotheken mit dem asyncSchlüsselwort zu durchsuchen . Bei so etwas wie asynchroner Programmierung lernen Sie am besten, wie andere es verwendet haben.