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 async
await
undasyncio
Python hat kürzlich zwei Schlüsselwörter hinzugefügt async
und await
zum 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 async
Schlü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,
await
mit dem eine Coroutine auf Ergebnisse einer anderen Coroutine warten kann, ohne sie zu blockieren. Bis die Ergebnisse von derawait
ed-Coroutine zurückkommen , wechselt Python frei zwischen anderen laufenden Coroutinen. - Coroutinen können nur von anderen
async
Funktionen aufgerufen werden. Wenn Sie den Hauptteil des Skripts ausführenserver_ops()
oder unverändert ausführenget_server_status()
, werden die Ergebnisse nicht angezeigt. Sie erhalten ein Python-Coroutine-Objekt, das nicht direkt verwendet werden kann.
Wenn wir also keine async
Funktionen von nicht asynchronen Funktionen aufrufen und keine async
Funktionen direkt ausführen können, wie verwenden wir sie? Antwort: Verwenden Sie die asyncio
Bibliothek, die async
den Rest von Python verbindet.
Python async
await
und asyncio
Beispiel
Hier ist ein Beispiel (wieder nicht funktional, aber illustrativ), wie man eine Web-Scraping-Anwendung mit async
und schreiben könnte asyncio
. Dieses Skript verwendet eine Liste von URLs und verwendet mehrere Instanzen einer async
Funktion 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 asyncio
Funktionen:
asyncio.run()
wird verwendet, um eineasync
Funktion aus dem nicht asynchronen Teil unseres Codes zu starten und damit alle asynchronen Aktivitäten des Programms zu starten. (So laufen wirmain()
.)asyncio.gather()
Nimmt eine oder mehrere asynchron dekorierte Funktionen (in diesem Fall mehrere Instanzenread_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 asyncio
Bibliothek zurückgreifen. Einige andere Elemente sind ebenfalls der Schlüssel zu asynchronen Anwendungen in Python:
Ereignisschleifen
Die asyncio
Bibliothek 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 Task
Objekt 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, usingasyncio.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 aTask
object to run it. Here we submit each URL as a separateTask
to the event loop, and store theTask
objects in a list. Note that we can only do this inside the event loop—that is, inside anasync
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 fromasyncio.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-libs
Repository verfügt über einige wichtige Elemente, z. B. die aiohittp
Bibliothek für den Webzugriff. Es lohnt sich auch, den Python-Paketindex nach Bibliotheken mit dem async
Schlüsselwort zu durchsuchen . Bei so etwas wie asynchroner Programmierung lernen Sie am besten, wie andere es verwendet haben.