PEP 3156 – Asynchronous IO Support Rebooted: das „asyncio“-Modul
- Autor:
- Guido van Rossum <guido at python.org>
- BDFL-Delegate:
- Antoine Pitrou <antoine at python.org>
- Discussions-To:
- python-tulip@googlegroups.com
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 12-Dez-2012
- Python-Version:
- 3.3
- Post-History:
- 21-Dez-2012
- Ersetzt:
- 3153
- Resolution:
- Python-Dev Nachricht
Inhaltsverzeichnis
- Zusammenfassung
- Einleitung
- Spezifikation der Event Loop-Schnittstelle
- Event Loop-Richtlinie: Aktuelle Event Loop abrufen und setzen
- Festlegen von Zeiten
- Eingebettete Event Loops
- Event Loop-Klassen
- Übersicht über Event Loop-Methoden
- Event Loop-Methoden
- Gegenseitiger Ausschluss von Callbacks
- Ausnahmen
- Debug-Modus
- Handles
- Server
- Futures
- Transports
- Protokolle
- Callback-Stil
- Coroutinen und der Scheduler
- Synchronisation
- Verschiedenes
- Wunschliste
- Offene Fragen
- Referenzen
- Danksagungen
- Urheberrecht
Zusammenfassung
Dies ist ein Vorschlag für asynchrone I/O in Python 3, beginnend mit Python 3.3. Betrachten Sie dies als den konkreten Vorschlag, der in PEP 3153 fehlt. Der Vorschlag beinhaltet eine austauschbare Event Loop, Transport- und Protokoll-Abstraktionen ähnlich denen in Twisted und einen höherstufigen Scheduler, der auf yield from (PEP 380) basiert. Der vorgeschlagene Paketname ist asyncio.
Einleitung
Status
Eine Referenzimplementierung existiert unter dem Codenamen Tulip. Das Tulip-Repository ist im Abschnitt Referenzen am Ende verlinkt. Pakete, die auf diesem Repository basieren, werden auf PyPI bereitgestellt (siehe Referenzen), um die Verwendung des asyncio-Pakets mit Python 3.3-Installationen zu ermöglichen.
Stand 20. Oktober 2013 wurde das asyncio-Paket in das Python 3.4-Repository eingecheckt und mit Python 3.4-alpha-4 veröffentlicht, mit „vorläufigem“ API-Status. Dies ist ein Ausdruck des Vertrauens und soll das frühe Feedback zur API erhöhen, und nicht die Akzeptanz des PEP erzwingen. Es wird erwartet, dass das Paket den vorläufigen Status in Python 3.4 beibehält und in Python 3.5 den finalen Status erreicht. Die Entwicklung findet weiterhin hauptsächlich im Tulip-Repository statt, mit gelegentlichen Übernahmen in das CPython-Repository.
Abhängigkeiten
Python 3.3 ist für viele der vorgeschlagenen Funktionen erforderlich. Die Referenzimplementierung (Tulip) erfordert keine neuen Sprach- oder Standardbibliotheksfunktionen über Python 3.3 hinaus, keine Drittanbieter-Module oder -Pakete und keinen C-Code, außer der (optionalen) IOCP-Unterstützung unter Windows.
Modul-Namensraum
Die hier spezifizierte Schnittstelle befindet sich in einem neuen Top-Level-Paket, asyncio. Verschiedene Komponenten befinden sich in separaten Untermodulen des Pakets. Das Paket importiert gängige APIs aus ihren jeweiligen Untermodulen und stellt sie als Paketattribute zur Verfügung (ähnlich wie das E-Mail-Paket funktioniert). Für solche gängigen APIs ist der Name des Untermoduls, das sie tatsächlich definiert, nicht Teil der Spezifikation. Weniger gängige APIs müssen möglicherweise explizit aus ihrem jeweiligen Untermodul importiert werden, und in diesem Fall ist der Modulname Teil der Spezifikation.
Klassen und Funktionen, die ohne Modulnamen definiert sind, werden angenommen, dass sie im Namensraum des Top-Level-Pakets leben. (Aber verwechseln Sie diese nicht mit Methoden verschiedener Klassen, die zur Kürze auch ohne Namespace-Präfix in bestimmten Kontexten verwendet werden.)
Interoperabilität
Die Event Loop ist der Ort, an dem die meiste Interoperabilität stattfindet. Es sollte einfach für (Python 3.3-Ports von) Frameworks wie Twisted, Tornado oder sogar Gevents sein, entweder die Standard-Event-Loop-Implementierung an ihre Bedürfnisse mit einem leichten Adapter oder Proxy anzupassen oder die Standard-Event-Loop-Implementierung durch eine Anpassung ihrer eigenen Event-Loop-Implementierung zu ersetzen. (Einige Frameworks, wie Twisted, haben mehrere Event-Loop-Implementierungen. Das sollte kein Problem sein, da diese alle dieselbe Schnittstelle haben.)
In den meisten Fällen sollte es möglich sein, dass zwei verschiedene Drittanbieter-Frameworks miteinander interagieren, entweder durch gemeinsame Nutzung der Standard-Event-Loop-Implementierung (jeder mit seinem eigenen Adapter) oder durch gemeinsame Nutzung der Event-Loop-Implementierung eines der Frameworks. Im letzteren Fall würden zwei Anpassungsebenen auftreten (von Framework A's Event Loop zur Standard-Event-Loop-Schnittstelle und von dort zur Event Loop von Framework B). Welche Event-Loop-Implementierung verwendet wird, sollte vom Hauptprogramm kontrolliert werden (obwohl eine Standardrichtlinie für die Auswahl der Event Loop bereitgestellt wird).
Damit diese Interoperabilität effektiv ist, ist die bevorzugte Richtung der Anpassung in Drittanbieter-Frameworks, die Standard-Event-Loop beizubehalten und sie an die API des Frameworks anzupassen. Idealerweise würden alle Drittanbieter-Frameworks ihre eigene Event-Loop-Implementierung zugunsten der Standardimplementierung aufgeben. Aber nicht alle Frameworks sind möglicherweise mit der Funktionalität der Standardimplementierung zufrieden.
Um beide Richtungen der Anpassung zu unterstützen, werden zwei separate APIs spezifiziert
- Eine Schnittstelle zur Verwaltung der aktuellen Event Loop
- Die Schnittstelle einer konformen Event Loop
Eine Event-Loop-Implementierung kann zusätzliche Methoden und Garantien bieten, solange diese in der Dokumentation als nicht-standardmäßig aufgeführt sind. Eine Event-Loop-Implementierung kann auch bestimmte Methoden nicht implementieren, wenn sie in der gegebenen Umgebung nicht implementiert werden können; solche Abweichungen von der Standard-API sollten jedoch nur als letzter Ausweg betrachtet werden und nur, wenn die Plattform oder Umgebung dies erzwingt. (Ein Beispiel wäre eine Plattform, auf der es eine System-Event-Loop gibt, die nicht gestartet oder gestoppt werden kann; siehe „Eingebettete Event Loops“ unten.)
Die Event-Loop-API hängt nicht von await/yield from ab. Vielmehr verwendet sie eine Kombination aus Callbacks, zusätzlichen Schnittstellen (Transports und Protokolle) und Futures. Letztere ähneln denen, die in PEP 3148 definiert sind, haben aber eine andere Implementierung und sind nicht an Threads gebunden. Insbesondere löst die result()-Methode eine Ausnahme aus, anstatt zu blockieren, wenn ein Ergebnis noch nicht bereit ist; der Benutzer wird erwartet, Callbacks (oder await/yield from) zu verwenden, um auf das Ergebnis zu warten.
Alle Event-Loop-Methoden, die als Rückgabe einer Coroutine spezifiziert sind, dürfen entweder einen Future oder eine Coroutine zurückgeben, je nach Wahl der Implementierung (die Standardimplementierung gibt immer Coroutinen zurück). Alle Event-Loop-Methoden, die dokumentiert sind, dass sie Coroutinen-Argumente akzeptieren, *müssen* sowohl Futures als auch Coroutinen für solche Argumente akzeptieren. (Eine Hilfsfunktion, ensure_future(), existiert, um ein Argument, das entweder eine Coroutine oder ein Future ist, in ein Future zu konvertieren.)
Für Benutzer (wie mich), die keine Callbacks mögen, wird ein Scheduler bereitgestellt, um asynchronen I/O-Code als Coroutinen zu schreiben, unter Verwendung von PEP 380 yield from oder PEP 492 await-Ausdrücken. Der Scheduler ist nicht austauschbar; die Austauschbarkeit findet auf der Ebene der Event Loop statt, und die Standard-Scheduler-Implementierung sollte mit jeder konformen Event-Loop-Implementierung funktionieren. (Tatsächlich ist dies ein wichtiger Prüfstein für konforme Implementierungen.)
Für die Interoperabilität zwischen Code, der mit Coroutinen geschrieben wurde, und anderen asynchronen Frameworks definiert der Scheduler eine Task-Klasse, die sich wie ein Future verhält. Ein Framework, das auf der Ebene der Event Loop interagiert, kann auf die Beendigung eines Futures warten, indem es einen Callback zum Future hinzufügt. Ebenso bietet der Scheduler eine Operation, um eine Coroutine auszusetzen, bis ein Callback aufgerufen wird.
Wenn ein solches Framework die Future- und Task-Klassen nicht wie sie sind verwenden kann, kann es die Methoden loop.create_future() und loop.create_task() neu implementieren. Diese sollten Objekte zurückgeben, die (eine Obermenge der) Future/Task-Schnittstellen implementieren.
Ein weniger ambitioniertes Framework kann einfach loop.set_task_factory() aufrufen, um die Task-Klasse zu ersetzen, ohne seine eigene Event Loop zu implementieren.
Die Event-Loop-API bietet eine begrenzte Interoperabilität mit Threads: es gibt eine API, um eine Funktion an einen Executor zu übergeben (siehe PEP 3148), die einen Future zurückgibt, der mit der Event Loop kompatibel ist, und es gibt eine Methode, um einen Callback thread-sicher von einem anderen Thread an eine Event Loop zu planen.
Transports und Protokolle
Für diejenigen, die mit Twisted nicht vertraut sind, ist eine kurze Erklärung der Beziehung zwischen Transports und Protokollen angebracht. Auf der höchsten Ebene ist der Transport dafür zuständig, *wie* Bytes übertragen werden, während das Protokoll bestimmt, *welche* Bytes übertragen werden (und bis zu einem gewissen Grad wann).
Anders ausgedrückt: Ein Transport ist eine Abstraktion für einen Socket (oder ein ähnliches I/O-Endpunkt), während ein Protokoll eine Abstraktion für eine Anwendung ist, aus Sicht des Transports.
Noch eine andere Sichtweise ist einfach, dass die Schnittstellen von Transport und Protokoll *zusammen* eine abstrakte Schnittstelle für die Verwendung von Netzwerk-I/O und Interprozess-I/O definieren.
Es gibt fast immer eine 1:1-Beziehung zwischen Transport- und Protokoll-Objekten: das Protokoll ruft Transportmethoden auf, um Daten zu senden, während der Transport Protokollmethoden aufruft, um empfangene Daten weiterzugeben. Weder Transport- noch Protokollmethoden „blockieren“ – sie setzen Ereignisse in Bewegung und kehren dann zurück.
Der häufigste Transpor-Typ ist ein bidirektionaler Stream-Transport. Er repräsentiert ein Paar von gepufferten Streams (einen in jede Richtung), die jeweils eine Sequenz von Bytes übertragen. Das häufigste Beispiel für einen bidirektionalen Stream-Transport ist wahrscheinlich eine TCP-Verbindung. Ein weiteres gängiges Beispiel ist eine SSL/TLS-Verbindung. Aber es gibt auch einige andere Dinge, die so betrachtet werden können, zum Beispiel eine SSH-Sitzung oder ein Paar UNIX-Pipes. Typischerweise gibt es nicht viele verschiedene Transport-Implementierungen, und die meisten davon werden mit der Event-Loop-Implementierung geliefert. Es gibt jedoch keine Anforderung, dass alle Transports durch Aufrufen einer Event-Loop-Methode erstellt werden müssen: ein Drittanbieter-Modul kann sehr wohl einen neuen Transport implementieren und einen Konstruktor oder eine Factory-Funktion dafür bereitstellen, die einfach eine Event Loop als Argument nimmt oder get_event_loop() aufruft.
Beachten Sie, dass Transports keine Sockets verwenden müssen, nicht einmal, wenn sie TCP verwenden – Sockets sind ein plattformspezifisches Implementierungsdetail.
Ein bidirektionaler Stream-Transport hat zwei „Enden“: ein Ende spricht mit dem Netzwerk (oder einem anderen Prozess oder einer beliebigen Low-Level-Schnittstelle, die es umhüllt), und das andere Ende spricht mit dem Protokoll. Ersteres verwendet jede notwendige API zur Implementierung des Transports; aber die Schnittstelle zwischen Transport und Protokoll wird durch diesen PEP standardisiert.
Ein Protokoll kann eine Art „Application-Level“-Protokoll wie HTTP oder SMTP darstellen; es kann auch eine Abstraktion implementieren, die von mehreren Protokollen oder einer ganzen Anwendung geteilt wird. Die primäre Schnittstelle eines Protokolls ist zum Transport. Während einige beliebte Protokolle (und andere Abstraktionen) Standardimplementierungen haben mögen, implementieren Anwendungen oft benutzerdefinierte Protokolle. Es macht auch Sinn, Bibliotheken nützlicher Drittanbieter-Protokoll-Implementierungen zu haben, die von PyPI heruntergeladen und installiert werden können.
Die allgemeine Vorstellung von Transport und Protokoll umfasst weitere Schnittstellen, bei denen der Transport eine andere Kommunikationsabstraktion umhüllt. Beispiele hierfür sind Schnittstellen zum Senden und Empfangen von Datagrammen (z. B. UDP) oder ein Subprozess-Manager. Die Trennung der Verantwortlichkeiten ist die gleiche wie bei bidirektionalen Stream-Transports und Protokollen, aber die spezifische Schnittstelle zwischen Transport und Protokoll ist in jedem Fall unterschiedlich.
Details zu den Schnittstellen, die von den verschiedenen Standardtypen von Transports und Protokollen definiert werden, werden später angegeben.
Spezifikation der Event Loop-Schnittstelle
Event Loop-Richtlinie: Aktuelle Event Loop abrufen und setzen
Die Verwaltung der Event Loop wird durch eine Event-Loop-Richtlinie gesteuert, die ein globales (pro Prozess) Objekt ist. Es gibt eine Standardrichtlinie und eine API, um die Richtlinie zu ändern. Eine Richtlinie definiert den Begriff des Kontexts; eine Richtlinie verwaltet für jeden Kontext eine separate Event Loop. Der Begriff des Kontexts der Standardrichtlinie ist als der aktuelle Thread definiert.
Bestimmte Plattformen oder Programmier-Frameworks können die Standardrichtlinie in etwas umändern, das den Erwartungen der Benutzer dieser Plattform oder dieses Frameworks besser entspricht. Solche Plattformen oder Frameworks müssen ihre Richtlinie dokumentieren und zu welchem Zeitpunkt während ihrer Initialisierungssequenz die Richtlinie gesetzt wird, um undefiniertes Verhalten zu vermeiden, wenn mehrere aktive Frameworks die Standardrichtlinie überschreiben wollen. (Siehe auch „Eingebettete Event Loops“ unten.)
Um die Event Loop für den aktuellen Kontext zu erhalten, verwenden Sie get_event_loop(). Dies gibt ein Event-Loop-Objekt zurück, das die unten spezifizierte Schnittstelle implementiert, oder löst eine Ausnahme aus, falls für den aktuellen Kontext keine Event Loop festgelegt wurde und die aktuelle Richtlinie keine Erstellung vorsieht. Es sollte niemals None zurückgeben.
Um die Event Loop für den aktuellen Kontext festzulegen, verwenden Sie set_event_loop(event_loop), wobei event_loop ein Event-Loop-Objekt ist, d. h. eine Instanz von AbstractEventLoop, oder None. Es ist in Ordnung, die aktuelle Event Loop auf None zu setzen, in welchem Fall nachfolgende Aufrufe von get_event_loop() eine Ausnahme auslösen werden. Dies ist nützlich zum Testen von Code, der nicht von der Existenz einer Standard-Event-Loop abhängen soll.
Es wird erwartet, dass get_event_loop() je nach Kontext ein anderes Event-Loop-Objekt zurückgibt (tatsächlich ist dies die Definition von Kontext). Es kann ein neues Event-Loop-Objekt erstellen, wenn keines gesetzt ist und die Erstellung durch die Richtlinie erlaubt ist. Die Standardrichtlinie erstellt eine neue Event Loop nur im Haupt-Thread (wie in threading.py definiert, das eine spezielle Unterklasse für den Haupt-Thread verwendet) und nur, wenn get_event_loop() aufgerufen wird, bevor set_event_loop() jemals aufgerufen wurde. (Um diesen Zustand zurückzusetzen, setzen Sie die Richtlinie zurück.) In anderen Threads muss eine Event Loop explizit gesetzt werden. Andere Richtlinien können sich anders verhalten. Die Erstellung von Event Loops durch die Standardrichtlinie ist träge; d. h. der erste Aufruf von get_event_loop() erstellt bei Bedarf eine Event-Loop-Instanz, wie von der aktuellen Richtlinie angegeben.
Zum Nutzen von Unit-Tests und anderen Sonderfällen gibt es eine dritte Richtlinienfunktion: new_event_loop(), die eine neue Event-Loop-Objekt gemäß den Standardregeln der Richtlinie erstellt und zurückgibt. Um dies zur aktuellen Event Loop zu machen, müssen Sie set_event_loop() damit aufrufen.
Um die Event-Loop-Richtlinie zu ändern, rufen Sie set_event_loop_policy(policy) auf, wobei policy ein Event-Loop-Richtlinienobjekt oder None ist. Wenn nicht None, muss das Richtlinienobjekt eine Instanz von AbstractEventLoopPolicy sein, die die Methoden get_event_loop(), set_event_loop(loop) und new_event_loop() definiert, die alle wie die oben beschriebenen Funktionen funktionieren.
Das Übergeben eines Richtlinienwerts von None stellt die Standard-Event-Loop-Richtlinie wieder her (und überschreibt die von der Plattform oder dem Framework gesetzte alternative Standardrichtlinie). Die Standard-Event-Loop-Richtlinie ist eine Instanz der Klasse DefaultEventLoopPolicy. Das aktuelle Event-Loop-Richtlinienobjekt kann durch Aufrufen von get_event_loop_policy() abgerufen werden.
TBD: Beschreibung von Child Watchern und UNIX-spezifischen Eigenheiten für die Subprozessverarbeitung.
Übergabe einer Event Loop explizit
Es ist möglich, Code zu schreiben, der eine Event Loop verwendet, ohne sich auf eine globale oder pro Thread Standard-Event-Loop zu verlassen. Zu diesem Zweck nehmen alle APIs, die Zugriff auf die aktuelle Event Loop benötigen (und keine Methoden einer Event-Klasse sind), ein optionales Schlüsselwortargument namens loop entgegen. Wenn dieses Argument None ist oder nicht angegeben wird, rufen solche APIs get_event_loop() auf, um die Standard-Event-Loop zu erhalten. Wenn das Schlüsselwortargument loop jedoch auf ein Event-Loop-Objekt gesetzt ist, wird dieses verwendet und an alle anderen solchen APIs weitergegeben, die es aufruft. Zum Beispiel wird Future(loop=my_loop) einen Future erstellen, der an die Event Loop my_loop gebunden ist. Wenn die Standard-aktuelle Event Loop None ist, ist das Schlüsselwortargument loop effektiv obligatorisch.
Beachten Sie, dass eine explizit übergebene Event Loop immer noch zum aktuellen Thread gehören muss; das Schlüsselwortargument loop ändert die Einschränkungen für die Verwendung einer Event Loop nicht magisch.
Festlegen von Zeiten
Wie üblich in Python werden alle Timeouts, Intervalle und Verzögerungen in Sekunden gemessen und können ganze Zahlen oder Gleitkommazahlen sein. Absolute Zeiten werden jedoch nicht als POSIX-Zeitstempel angegeben. Die Genauigkeit, Präzision und Epoche der Uhr sind der Implementierung überlassen.
Die Standardimplementierung verwendet time.monotonic(). Über die Auswirkungen dieser Wahl könnten Bücher geschrieben werden. Lesen Sie besser die Dokumentation für das time-Modul der Standardbibliothek.
Eingebettete Event Loops
Auf einigen Plattformen wird eine Event Loop vom System bereitgestellt. Eine solche Loop kann bereits laufen, wenn der Benutzercode startet, und es kann keine Möglichkeit geben, sie zu stoppen oder zu schließen, ohne das Programm zu beenden. In diesem Fall sind die Methoden zum Starten, Stoppen und Schließen der Event Loop möglicherweise nicht implementierbar und is_running() gibt möglicherweise immer True zurück.
Event Loop-Klassen
Es gibt keine tatsächliche Klasse namens EventLoop. Es gibt eine Klasse AbstractEventLoop, die alle Methoden ohne Implementierungen definiert und hauptsächlich als Dokumentation dient. Die folgenden konkreten Klassen sind definiert:
SelectorEventLoopist eine konkrete Implementierung der vollständigen API, die auf demselectors-Modul basiert (neu in Python 3.4). Der Konstruktor nimmt ein optionales Argument entgegen, einselectors.Selector-Objekt. Standardmäßig wird eine Instanz vonselectors.DefaultSelectorerstellt und verwendet.ProactorEventLoopist eine konkrete Implementierung der API mit Ausnahme der Methoden zur I/O-Ereignisbehandlung und Signalbehandlung. Sie ist nur unter Windows definiert (oder auf anderen Plattformen, die eine ähnliche API für „overlapped I/O“ unterstützen). Der Konstruktor nimmt ein optionales Argument entgegen, einProactor-Objekt. Standardmäßig wird eine Instanz vonIocpProactorerstellt und verwendet. (Die KlasseIocpProactorwird von diesem PEP nicht spezifiziert; sie ist lediglich ein Implementierungsdetail der KlasseProactorEventLoop.)
Übersicht über Event Loop-Methoden
Die Methoden einer konformen Event Loop sind in mehrere Kategorien unterteilt. Die erste Gruppe von Kategorien muss von allen konformen Event-Loop-Implementierungen unterstützt werden, mit der Ausnahme, dass eingebettete Event Loops die Methoden zum Starten, Stoppen und Schließen möglicherweise nicht implementieren. (Eine teilweise konforme Event Loop ist jedoch besser als keine. :-)
- Starten, Stoppen und Schließen:
run_forever(),run_until_complete(),stop(),is_running(),close(),is_closed(). - Grundlegende und zeitgesteuerte Callbacks:
call_soon(),call_later(),call_at(),time(). - Thread-Interaktion:
call_soon_threadsafe(),run_in_executor(),set_default_executor(). - Internet-Namensauflösung:
getaddrinfo(),getnameinfo(). - Internetverbindungen:
create_connection(),create_server(),create_datagram_endpoint(). - Methoden für Wrapper-Sockets:
sock_recv(),sock_sendall(),sock_connect(),sock_accept(). - Task- und Future-Unterstützung:
create_future(),create_task(),set_task_factory(),get_task_factory(). - Fehlerbehandlung:
get_exception_handler(),set_exception_handler(),default_exception_handler(),call_exception_handler(). - Debug-Modus:
get_debug(),set_debug().
Die zweite Gruppe von Kategorien *kann* von konformen Event-Loop-Implementierungen unterstützt werden. Wenn sie nicht unterstützt werden, lösen sie NotImplementedError aus. (In der Standardimplementierung unterstützt SelectorEventLoop auf UNIX-Systemen all diese; SelectorEventLoop unter Windows unterstützt die Kategorie der I/O-Ereignisbehandlung; ProactorEventLoop unter Windows unterstützt die Kategorie Pipes und Subprozesse.)
- I/O-Callbacks:
add_reader(),remove_reader(),add_writer(),remove_writer(). - Pipes und Subprozesse:
connect_read_pipe(),connect_write_pipe(),subprocess_shell(),subprocess_exec(). - Signal-Callbacks:
add_signal_handler(),remove_signal_handler().
Event Loop-Methoden
Starten, Stoppen und Schließen
Eine (nicht geschlossene) Event Loop kann sich in einem von zwei Zuständen befinden: laufend oder gestoppt. Diese Methoden befassen sich mit dem Starten und Stoppen einer Event Loop
run_forever(). Führt die Event Loop aus, bisstop()aufgerufen wird. Dies kann nicht aufgerufen werden, wenn die Event Loop bereits läuft. (Dies hat einen langen Namen, teilweise um Verwechslungen mit früheren Versionen dieses PEP zu vermeiden, in denenrun()ein anderes Verhalten hatte, teilweise, weil es bereits zu viele APIs gibt, die eine Methode namensrun()haben, und teilweise, weil sowieso nicht viele Stellen dies aufrufen sollten.)run_until_complete(future). Führt die Event Loop aus, bis der Future abgeschlossen ist. Wenn der Future abgeschlossen ist, wird sein Ergebnis zurückgegeben oder seine Ausnahme ausgelöst. Dies kann nicht aufgerufen werden, wenn die Event Loop bereits läuft. Die Methode erstellt ein neuesTask-Objekt, wenn der Parameter eine Coroutine ist.stop(). Stoppt die Event Loop, sobald es angebracht ist. Es ist in Ordnung, die Loop danach mitrun_forever()oderrun_until_complete()neu zu starten; keine geplanten Callbacks gehen verloren, wenn dies geschieht. Hinweis:stop()kehrt normal zurück und der aktuelle Callback darf fortgesetzt werden. Wie schnell danach die Event Loop stoppt, hängt von der Implementierung ab, aber die Absicht ist, kurz vor dem Abfragen von I/O zu stoppen und keine zukünftig geplanten Callbacks auszuführen; die größte Freiheit, die eine Implementierung hat, ist, wie viel von der „Ready Queue“ (mitcall_soon()geplante Callbacks) sie vor dem Stoppen verarbeitet.is_running(). GibtTruezurück, wenn die Event Loop gerade läuft, undFalse, wenn sie gestoppt ist.close(). Schließt die Event Loop und gibt alle Ressourcen frei, die sie möglicherweise hält, wie z. B. den vonepoll()oderkqueue()verwendeten Dateideskriptor und den Standard-Executor. Dies sollte nicht aufgerufen werden, während die Event Loop läuft. Nach dem Aufruf sollte die Event Loop nicht mehr verwendet werden. Sie kann mehrmals aufgerufen werden; nachfolgende Aufrufe sind No-Ops.is_closed(). GibtTruezurück, wenn die Event Loop geschlossen ist, andernfallsFalse. (Hauptsächlich für die Fehlerberichterstattung gedacht; bitte implementieren Sie keine Funktionalität basierend auf dieser Methode.)
Grundlegende Callbacks
Callbacks, die mit derselben Event Loop verbunden sind, werden streng serialisiert: ein Callback muss abgeschlossen sein, bevor der nächste aufgerufen wird. Dies ist eine wichtige Garantie: wenn zwei oder mehr Callbacks gemeinsame Zustände verwenden oder modifizieren, ist jeder Callback garantiert, dass er, während er läuft, der gemeinsame Zustand nicht von einem anderen Callback geändert wird.
call_soon(callback, *args). Dies plant einen Callback, der so bald wie möglich aufgerufen wird. Gibt einHandle(siehe unten) zurück, das den Callback repräsentiert und dessencancel()-Methode zum Abbrechen des Callbacks verwendet werden kann. Es garantiert, dass Callbacks in der Reihenfolge aufgerufen werden, in der sie geplant wurden.call_later(delay, callback, *args). Ordnet an, dasscallback(*args)ungefährdelaySekunden in der Zukunft einmal aufgerufen wird, es sei denn, er wird abgebrochen. Gibt einHandlezurück, das den Callback repräsentiert und dessencancel()-Methode zum Abbrechen des Callbacks verwendet werden kann. In der Vergangenheit geplante oder exakt zur gleichen Zeit geplante Callbacks werden in undefinierter Reihenfolge aufgerufen.call_at(wann, callback, *args). Dies ist wiecall_later(), aber die Zeit wird als absolute Zeit ausgedrückt. Gibt ein ähnlichesHandlezurück. Es gibt eine einfache Entsprechung:loop.call_later(delay, callback, *args)ist dasselbe wieloop.call_at(loop.time() + delay, callback, *args).time(). Gibt die aktuelle Zeit gemäß der Uhr der Ereignisschleife zurück. Dies kanntime.time()odertime.monotonic()oder eine andere systemspezifische Uhr sein, muss aber ein Float zurückgeben, das die Zeit in Einheiten von ungefähr einer Sekunde seit einem bestimmten Epoche ausdrückt. (Keine Uhr ist perfekt – siehe PEP 418.)
Hinweis: Eine frühere Version dieses PEP definierte eine Methode namens call_repeatedly(), die versprach, einen Callback in regelmäßigen Abständen aufzurufen. Dies wurde zurückgezogen, da das Design einer solchen Funktion zu übermäßig spezifiziert ist. Einerseits kann eine einfache Timer-Schleife leicht durch einen Callback emuliert werden, der sich selbst mit call_later() neu plant; es ist auch einfach, eine Coroutine zu schreiben, die eine Schleife und einen sleep()-Aufruf (eine Top-Level-Funktion im Modul, siehe unten) enthält. Andererseits gibt es aufgrund der Komplexität der genauen Zeitmessung viele Fallen und Fallstricke für Unvorsichtige (siehe PEP 418), und verschiedene Anwendungsfälle erfordern unterschiedliche Verhaltensweisen in Grenzsituationen. Es ist unmöglich, eine API für diesen Zweck anzubieten, die in allen Fällen kugelsicher ist, daher wird es als besser erachtet, Anwendungsdesignern selbst entscheiden zu lassen, welche Art von Timer-Schleife sie implementieren möchten.
Thread-Interaktion
call_soon_threadsafe(callback, *args). Wiecall_soon(callback, *args), aber wenn von einem anderen Thread aufgerufen, während die Ereignisschleife auf E/A wartet, wird die Ereignisschleife entsperrt. Gibt einHandlezurück. Dies ist die *einzige* Methode, die sicher von einem anderen Thread aufgerufen werden kann. (Um einen Callback auf Thread-sichere Weise für eine spätere Zeit zu planen, können Sieloop.call_soon_threadsafe(loop.call_later, when, callback, *args)verwenden.) Hinweis: Dies ist nicht sicher aus einem Signal-Handler aufzurufen (da es Sperren verwenden kann). Tatsächlich ist keine API Signal-sicher; wenn Sie Signale verarbeiten möchten, verwenden Sieadd_signal_handler(), das unten beschrieben wird.run_in_executor(executor, callback, *args). Ordnet den Aufruf voncallback(*args)in einem Executor an (siehe PEP 3148). Gibt eineasyncio.Future-Instanz zurück, deren Ergebnis im Erfolgsfall der Rückgabewert dieses Aufrufs ist. Dies ist äquivalent zuwrap_future(executor.submit(callback, *args)). WennexecutorNoneist, wird der Standard-Executor verwendet, der vonset_default_executor()festgelegt wurde. Wenn noch kein Standard-Executor festgelegt wurde, wird einThreadPoolExecutormit einer Standardanzahl von Threads erstellt und als Standard-Executor festgelegt. (Die Standardimplementierung verwendet in diesem Fall 5 Threads.)set_default_executor(executor). Legt den Standard-Executor fest, der vonrun_in_executor()verwendet wird. Das Argument muss eine PEP 3148Executor-Instanz oderNonesein, um den Standard-Executor zurückzusetzen.
Siehe auch die Funktion wrap_future(), die im Abschnitt über Futures beschrieben wird.
Internet-Namensauflösung
Diese Methoden sind nützlich, wenn Sie einen Socket mit einer Adresse verbinden oder binden möchten, ohne das Risiko einer Blockierung für die Namensauflösung einzugehen. Sie werden normalerweise implizit von create_connection(), create_server() oder create_datagram_endpoint() aufgerufen.
getaddrinfo(host, port, family=0, type=0, proto=0, flags=0). Ähnlich wie die Funktionsocket.getaddrinfo(), gibt aber einen Future zurück. Das Ergebnis des Futures im Erfolgsfall ist eine Liste im selben Format wie die vonsocket.getaddrinfo()zurückgegebene Liste, d.h. eine Liste von(address_family, socket_type, socket_protocol, canonical_name, address), wobeiaddressein 2-Tupel(ipv4_address, port)für IPv4-Adressen und ein 4-Tupel(ipv6_address, port, flow_info, scope_id)für IPv6-Adressen ist. Wenn dasfamily-Argument Null oder nicht spezifiziert ist, kann die zurückgegebene Liste eine Mischung aus IPv4- und IPv6-Adressen enthalten; andernfalls werden die zurückgegebenen Adressen durch denfamily-Wert eingeschränkt (ähnlich fürprotoundflags). Die Standardimplementierung ruftsocket.getaddrinfo()überrun_in_executor()auf, aber andere Implementierungen können wählen, ihre eigene DNS-Auflösung zu implementieren. Die optionalen Argumente *müssen* als Schlüsselwortargumente angegeben werden.Hinweis: Implementierungen dürfen einen Teil der vollständigen Schnittstelle socket.getaddrinfo() implementieren; z. B. unterstützen sie möglicherweise keine symbolischen Portnamen, oder sie ignorieren oder implementieren die Argumente
type,protoundflagsnur unvollständig. Wenn jedochtypeundprotoignoriert werden, sollten die übergebenen Argumentwerte unverändert in die Elementesocket_typeundsocket_protocolder Rückgabetupel kopiert werden. (familydarf nicht ignoriert werden, da IPv4- und IPv6-Adressen unterschiedlich aufgelöst werden müssen. Die einzig zulässigen Werte fürfamilysindsocket.AF_UNSPEC(0),socket.AF_INETundsocket.AF_INET6, und letzteres nur, wenn es von der Plattform definiert wird.)getnameinfo(sockaddr, flags=0). Ähnlich wiesocket.getnameinfo(), gibt aber einen Future zurück. Das Ergebnis des Futures im Erfolgsfall ist ein Tupel(host, port). Gleiche Implementierungsbemerkungen wie fürgetaddrinfo().
Internetverbindungen
Dies sind die High-Level-Schnittstellen für die Verwaltung von Internetverbindungen. Ihre Verwendung wird gegenüber den entsprechenden Low-Level-Schnittstellen empfohlen, da sie die Unterschiede zwischen Selector-basierten und Proactor-basierten Ereignisschleifen abstrahieren.
Beachten Sie, dass die Client- und Serverseite von Stream-Verbindungen dieselbe Transport- und Protokollschnittstelle verwenden. Datagramm-Endpunkte verwenden jedoch eine andere Transport- und Protokollschnittstelle.
create_connection(protocol_factory, host, port, <options>). Erstellt eine Stream-Verbindung zu einem gegebenen Internet-Host und -Port. Dies ist eine Aufgabe, die typischerweise von der Client-Seite der Verbindung aufgerufen wird. Sie erstellt einen implementierungsabhängigen bidirektionalen Stream-Transport zur Darstellung der Verbindung, ruft dannprotocol_factory()auf, um die Protokollimplementierung des Benutzers zu instanziieren (oder abzurufen) und bindet schließlich die beiden zusammen. (Siehe unten für die Definitionen von Transport und Protokoll.) Die Protokollimplementierung des Benutzers wird durch Aufrufen vonprotocol_factory()ohne Argumente (*) erstellt oder abgerufen. Das Ergebnis der Coroutine im Erfolgsfall ist das(transport, protocol)-Paar; wenn ein Fehler die Erstellung einer erfolgreichen Verbindung verhindert, wird eine entsprechende Ausnahme ausgelöst. Beachten Sie, dass beim Abschluss der Coroutine die Methodeconnection_made()des Protokolls noch nicht aufgerufen wurde; dies geschieht, wenn der Verbindungs-Handshake abgeschlossen ist.(*) Es besteht keine Anforderung, dass
protocol_factoryeine Klasse ist. Wenn Ihre Protokollklasse spezifische Argumente an ihren Konstruktor übergeben muss, können Sielambdaverwenden. Sie können auch eine trivialelambdaübergeben, die eine zuvor erstellte Protokollinstanz zurückgibt.Die <options> werden alle über optionale Schlüsselwortargumente spezifiziert
ssl: Übergeben SieTrue, um einen SSL/TLS-Transport zu erstellen (standardmäßig wird ein einfacher TCP-Transport erstellt). Oder übergeben Sie einssl.SSLContext-Objekt, um das Standard-SSL-Kontextobjekt zu überschreiben. Wenn ein Standardkontext erstellt wird, obliegt es der Implementierung, angemessene Standardwerte zu konfigurieren. Die Referenzimplementierung verwendet derzeitPROTOCOL_SSLv23und setzt die OptionOP_NO_SSLv2, ruftset_default_verify_paths()auf und setztverify_modeaufCERT_REQUIRED. Zusätzlich, immer wenn der Kontext (Standard oder ein anderer) einenverify_modevonCERT_REQUIREDoderCERT_OPTIONALangibt, und wenn ein Hostname angegeben wird, wird unmittelbar nach einem erfolgreichen Handshakessl.match_hostname(peercert, hostname)aufgerufen, und wenn dies eine Ausnahme auslöst, wird die Verbindung geschlossen. (Um dieses Verhalten zu vermeiden, übergeben Sie einen SSL-Kontext, dessenverify_modeaufCERT_NONEgesetzt ist. Dies bedeutet jedoch, dass Sie nicht sicher sind und z. B. Man-in-the-Middle-Angriffen ausgesetzt sind.)family,proto,flags: Adressfamilie, Protokoll und Flags, die angetaddrinfo()weitergegeben werden. Diese alle standardmäßig auf0, was bedeutet "nicht spezifiziert". (Der Socket-Typ ist immerSOCK_STREAM.) Wenn einer dieser Werte nicht angegeben ist, wählt die Methodegetaddrinfo()geeignete Werte aus. Hinweis:protohat nichts mit dem High-Level-Protokollkonzept oder dem Argumentprotocol_factoryzu tun.sock: Ein optionaler Socket, der anstelle der Argumentehost,port,family,protoundflagsverwendet wird. Wenn dies angegeben ist, müssenhostundportexplizit aufNonegesetzt werden.local_addr: Wenn angegeben, ein(host, port)-Tupel, das zum Binden des Sockets lokal verwendet wird. Dies ist selten nötig, aber auf Multi-Homed-Servern müssen Sie gelegentlich erzwingen, dass eine Verbindung von einer bestimmten Adresse kommt. So würden Sie das tun. Der Host und der Port werden mitgetaddrinfo()aufgelöst.server_hostname: Dies ist nur relevant, wenn SSL/TLS verwendet wird; es sollte nicht verwendet werden, wennsslnicht gesetzt ist. Wennsslgesetzt ist, legt dies den zu verifizierenden Hostnamen fest oder überschreibt ihn. Standardmäßig wird der Wert des Argumentshostverwendet. Wennhostleer ist, gibt es keinen Standardwert und Sie müssen einen Wert fürserver_hostnameangeben. Um die Hostnamenverifizierung zu deaktivieren (was ein ernstes Sicherheitsrisiko darstellt), müssen Sie hier einen leeren String übergeben und einssl.SSLContext-Objekt übergeben, dessenverify_modeaufssl.CERT_NONEgesetzt ist, als Argumentssl.
create_server(protocol_factory, host, port, <options>). Tritt in eine Serving-Schleife ein, die Verbindungen akzeptiert. Dies ist eine Coroutine, die abgeschlossen wird, sobald die Serving-Schleife zum Servieren eingerichtet ist. Der Rückgabewert ist einServer-Objekt, das verwendet werden kann, um die Serving-Schleife auf kontrollierte Weise zu stoppen (siehe unten). Mehrere Sockets können gebunden werden, wenn die angegebene Adresse sowohl IPv4- als auch IPv6-Verbindungen zulässt.Jedes Mal, wenn eine Verbindung akzeptiert wird, wird
protocol_factoryohne Argumente (**) aufgerufen, um ein Protokoll zu erstellen, ein bidirektionaler Stream-Transport wird erstellt, um die Netzwerkseite der Verbindung darzustellen, und die beiden werden durch Aufruf vonprotocol.connection_made(transport)zusammengebunden.(**) Siehe vorherige Fußnote für
create_connection(). Da jedochprotocol_factory()einmal für jede neue eingehende Verbindung aufgerufen wird, sollte es jedes Mal, wenn es aufgerufen wird, ein neues Protokollobjekt zurückgeben.Die <options> werden alle über optionale Schlüsselwortargumente spezifiziert
ssl: Übergeben Sie einssl.SSLContext-Objekt (oder ein Objekt mit derselben Schnittstelle), um das Standard-SSL-Kontextobjekt zu überschreiben. (Im Gegensatz zucreate_connection()ist das Übergeben vonTruehier nicht sinnvoll – dasSSLContext-Objekt wird benötigt, um das Zertifikat und den Schlüssel anzugeben.)backlog: Wert für die Warteschlange, der an denlisten()-Aufruf übergeben wird. Der Standardwert ist implementierungsabhängig; in der Standardimplementierung beträgt der Standardwert100.reuse_address: Ob die OptionSO_REUSEADDRfür den Socket gesetzt werden soll. Der Standardwert istTrueunter UNIX,Falseunter Windows.family,flags: Adressfamilie und Flags, die angetaddrinfo()weitergegeben werden. Die Familie standardmäßig aufAF_UNSPEC; die Flags standardmäßig aufAI_PASSIVE. (Der Socket-Typ ist immerSOCK_STREAM; das Socket-Protokoll immer auf0gesetzt, damitgetaddrinfo()wählen kann.)sock: Ein optionaler Socket, der anstelle der Argumentehost,port,familyundflagsverwendet wird. Wenn dies angegeben ist, müssenhostundportexplizit aufNonegesetzt werden.
create_datagram_endpoint(protocol_factory, local_addr=None, remote_addr=None, <options>). Erstellt einen Endpunkt zum Senden und Empfangen von Datagrammen (typischerweise UDP-Paketen). Aufgrund der Natur von Datagramm-Verkehr gibt es keine separaten Aufrufe zur Einrichtung von Client- und Serverseite, da normalerweise ein einzelner Endpunkt sowohl als Client als auch als Server fungiert. Dies ist eine Coroutine, die im Erfolgsfall ein(transport, protocol)-Paar zurückgibt oder im Fehlerfall eine Ausnahme auslöst. Wenn die Coroutine erfolgreich zurückkehrt, ruft der Transport im Protokoll Callbacks auf, wann immer ein Datagramm empfangen oder der Socket geschlossen wird; es liegt am Protokoll, Methoden auf dem Protokoll aufzurufen, um Datagramme zu senden. Der zurückgegebene Transport ist einDatagramTransport. Das zurückgegebene Protokoll ist einDatagramProtocol. Diese werden später beschrieben.Obligatorisches Positionsargument
protocol_factory: Eine Klasse oder Fabrikfunktion, die genau einmal ohne Argumente aufgerufen wird, um das zurückzugebende Protokollobjekt zu erstellen. Die Schnittstelle zwischen Datagramm-Transport und Protokoll wird unten beschrieben.
Optionale Argumente, die positionell oder als Schlüsselwortargumente angegeben werden können
local_addr: Ein optionales Tupel, das die Adresse angibt, an die der Socket gebunden wird. Wenn angegeben, muss dies ein(host, port)-Paar sein. Es wird angetaddrinfo()übergeben, um aufgelöst zu werden, und das Ergebnis wird an diebind()-Methode des erstellten Sockets übergeben. Wenngetaddrinfo()mehr als eine Adresse zurückgibt, werden diese nacheinander versucht. Wenn dies weggelassen wird, wird keinbind()-Aufruf erfolgen.remote_addr: Ein optionales Tupel, das die Adresse angibt, an die der Socket "verbunden" wird. (Da es keine solche Sache wie eine Datagramm-Verbindung gibt, gibt dies nur einen Standardwert für die Zieladresse ausgehender Datagramme an.) Wenn angegeben, muss dies ein(host, port)-Paar sein. Es wird angetaddrinfo()übergeben, um aufgelöst zu werden, und das Ergebnis wird zusammen mit dem erstellten Socket ansock_connect()übergeben. Wenngetaddrinfo()mehr als eine Adresse zurückgibt, werden diese nacheinander versucht. Wenn dies weggelassen wird, wird keinsock_connect()-Aufruf erfolgen.
Die <options> werden alle über optionale Schlüsselwortargumente spezifiziert
family,proto,flags: Adressfamilie, Protokoll und Flags, die angetaddrinfo()weitergegeben werden. Diese alle standardmäßig auf0, was bedeutet "nicht spezifiziert". (Der Socket-Typ ist immerSOCK_DGRAM.) Wenn einer dieser Werte nicht angegeben ist, wählt die Methodegetaddrinfo()geeignete Werte aus.
Beachten Sie, dass, wenn sowohl
local_addrals auchremote_addrvorhanden sind, alle Kombinationen von lokalen und entfernten Adressen mit übereinstimmender Adressfamilie versucht werden.
Methoden für Wrapper-Sockets
Die folgenden Methoden für asynchrone E/A an Sockets sind nicht für den allgemeinen Gebrauch bestimmt. Sie sind hauptsächlich für Transportimplementierungen gedacht, die mit IOCP über die Klasse ProactorEventLoop arbeiten. Sie sind jedoch leicht für andere Ereignisschleifen-Typen implementierbar, daher gibt es keinen Grund, sie nicht zu verlangen. Das Socket-Argument muss ein nicht-blockierender Socket sein.
sock_recv(sock, n). Empfängt bis zunBytes vom Socketsock. Gibt einen Future zurück, dessen Ergebnis im Erfolgsfall ein Bytes-Objekt ist.sock_sendall(sock, data). Sendet Bytesdataan den Socketsock. Gibt einen Future zurück, dessen Ergebnis im ErfolgsfallNoneist. Hinweis: Der Name verwendetsendallanstelle vonsend, um widerzuspiegeln, dass die Semantik und Signatur dieser Methode denen der Standardbibliotheks-Socketmethodesendall()und nichtsend()entsprechen.sock_connect(sock, address). Verbindet sich mit der angegebenen Adresse. Gibt einen Future zurück, dessen Ergebnis im ErfolgsfallNoneist.sock_accept(sock). Akzeptiert eine Verbindung von einem Socket. Der Socket muss im Zuhörmodus sein und an eine Adresse gebunden sein. Gibt einen Future zurück, dessen Ergebnis im Erfolgsfall ein Tupel(conn, peer)ist, wobeiconnein verbundener nicht-blockierender Socket undpeerdie Peer-Adresse ist.
I/O-Callbacks
Diese Methoden sind hauptsächlich für Transportimplementierungen gedacht, die mit einem Selektor arbeiten. Sie werden von SelectorEventLoop implementiert, aber nicht von ProactorEventLoop. Benutzerdefinierte Ereignisschleifen-Implementierungen können sie implementieren oder auch nicht.
Die unten genannten fd-Argumente können ganzzahlige Dateideskriptoren oder "file-ähnliche" Objekte mit einer fileno()-Methode sein, die ganzzahlige Dateideskriptoren umschließen. Nicht alle file-ähnlichen Objekte oder Dateideskriptoren sind akzeptabel. Sockets (und Socket-Dateideskriptoren) werden immer akzeptiert. Unter Windows werden keine anderen Typen unterstützt. Unter UNIX werden auch Pipes und möglicherweise TTY-Geräte unterstützt, aber keine Festplattendateien. Genau welche speziellen Dateitypen unterstützt werden, kann je nach Plattform und Implementierung des Selektors variieren. (Experimentell gibt es mindestens eine Art von Pseudo-TTY auf OS X, die von select und poll, aber nicht von kqueue unterstützt wird: sie wird von Emacs Shell-Fenstern verwendet.)
add_reader(fd, callback, *args). Ordnet an, dasscallback(*args)aufgerufen wird, wann immer der Dateideskriptorfdzum Lesen bereit ist. Der Aufruf vonadd_reader()erneut für denselben Dateideskriptor impliziert einen Aufruf vonremove_reader()für denselben Dateideskriptor.add_writer(fd, callback, *args). Wieadd_reader(), registriert den Callback aber zum Schreiben statt zum Lesen.remove_reader(fd). Bricht den aktuellen Lese-Callback für den Dateideskriptorfdab, falls einer gesetzt ist. Wenn derzeit kein Callback für den Dateideskriptor gesetzt ist, ist dies ein No-Op und gibtFalsezurück. Andernfalls entfernt es die Callback-Anordnung und gibtTruezurück.remove_writer(fd). Dies verhält sich zuadd_writer(), wie sichremove_reader()zuadd_reader()verhält.
Pipes und Subprozesse
Diese Methoden werden von SelectorEventLoop unter UNIX und ProactorEventLoop unter Windows unterstützt.
Die mit Pipes und Subprozessen verwendeten Transports und Protokolle unterscheiden sich von denen regulärer Stream-Verbindungen. Diese werden später beschrieben.
Jede der unten aufgeführten Methoden hat ein Argument protocol_factory, ähnlich wie bei create_connection(); dies wird genau einmal ohne Argumente aufgerufen, um das zurückzugebende Protokollobjekt zu erstellen.
Jede Methode ist eine Coroutine, die im Erfolgsfall ein (transport, protocol)-Paar zurückgibt oder im Fehlerfall eine Ausnahme auslöst.
connect_read_pipe(protocol_factory, pipe): Erzeugt eine unidirektionale Stream-Verbindung aus einem Dateiobjekt, das das Leseende einer UNIX-Pipe umschließt, welche im nicht-blockierenden Modus sein muss. Der zurückgegebene Transport ist einReadTransport.connect_write_pipe(protocol_factory, pipe): Erzeugt eine unidirektionale Stream-Verbindung aus einem Dateiobjekt, das das Schreibende einer UNIX-Pipe umschließt, welche im nicht-blockierenden Modus sein muss. Der zurückgegebene Transport ist einWriteTransport; er hat keine Lese-bezogenen Methoden. Das zurückgegebene Protokoll ist einBaseProtocol.subprocess_shell(protocol_factory, cmd, <options>): Erzeugt einen Subprozess auscmd, einer Zeichenkette, die die "Shell"-Syntax der Plattform verwendet. Dies ähnelt der Klassesubprocess.Popen()der Standardbibliothek, die mitshell=Trueaufgerufen wird. Die restlichen Argumente und der Rückgabewert werden nachfolgend beschrieben.subprocess_exec(protocol_factory, *args, <options>): Erzeugt einen Subprozess aus einem oder mehreren Zeichenkettenargumenten, wobei die erste Zeichenkette das auszuführende Programm angibt und die restlichen Zeichenketten die Argumente des Programms angeben. (Zusammen bilden die Zeichenkettenargumente somit densys.argv-Wert des Programms, vorausgesetzt, es handelt sich um ein Python-Skript.) Dies ähnelt der Klassesubprocess.Popen()der Standardbibliothek, die mitshell=Falseund der Liste von Zeichenketten als erstes Argument aufgerufen wird; wo jedochPopen()ein einzelnes Argument erhält, das eine Liste von Zeichenketten ist, erhältsubprocess_exec()mehrere Zeichenkettenargumente. Die restlichen Argumente und der Rückgabewert werden nachfolgend beschrieben.
Abgesehen von der Art und Weise, wie das auszuführende Programm spezifiziert wird, verhalten sich die beiden subprocess_*() Methoden gleich. Der zurückgegebene Transport ist ein SubprocessTransport, der eine andere Schnittstelle hat als der übliche bidirektionale Stream-Transport. Das zurückgegebene Protokoll ist ein SubprocessProtocol, das ebenfalls eine benutzerdefinierte Schnittstelle hat.
Die <options> werden alle über optionale Schlüsselwortargumente spezifiziert
stdin: Entweder ein Dateiobjekt, das die Pipe repräsentiert, die mit dem Standardeingabe-Stream des Subprozesses überconnect_write_pipe()verbunden wird, oder die Konstantesubprocess.PIPE(Standard). Standardmäßig wird eine neue Pipe erstellt und verbunden.stdout: Entweder ein Dateiobjekt, das die Pipe repräsentiert, die mit dem Standardausgabe-Stream des Subprozesses überconnect_read_pipe()verbunden wird, oder die Konstantesubprocess.PIPE(Standard). Standardmäßig wird eine neue Pipe erstellt und verbunden.stderr: Entweder ein Dateiobjekt, das die Pipe repräsentiert, die mit dem Standardfehlerausgabe-Stream des Subprozesses überconnect_read_pipe()verbunden wird, oder eine der Konstantensubprocess.PIPE(Standard) odersubprocess.STDOUT. Standardmäßig wird eine neue Pipe erstellt und verbunden. Wennsubprocess.STDOUTangegeben ist, wird der Standardfehlerausgabe-Stream des Subprozesses mit derselben Pipe wie der Standardausgabe-Stream verbunden.bufsize: Die Puffergröße, die beim Erstellen einer Pipe verwendet wird; diese wird ansubprocess.Popen()übergeben. In der Standardimplementierung ist der Standardwert Null, und unter Windows muss er Null sein; diese Standardwerte weichen vonsubprocess.Popen()ab.executable,preexec_fn,close_fds,cwd,env,startupinfo,creationflags,restore_signals,start_new_session,pass_fds: Diese optionalen Argumente werden ohne Interpretation ansubprocess.Popen()übergeben.
Signal-Callbacks
Diese Methoden werden nur unter UNIX unterstützt.
add_signal_handler(sig, callback, *args). Immer wenn das Signalsigempfangen wird, wird arrangiert, dasscallback(*args)aufgerufen wird. Die Angabe eines anderen Callbacks für dasselbe Signal ersetzt den vorherigen Handler (nur ein Handler kann pro Signal aktiv sein).sigmuss eine gültige Signalnummer sein, die im Modulsignaldefiniert ist. Wenn das Signal nicht behandelt werden kann, wird eine Ausnahme ausgelöst:ValueError, wenn es sich um kein gültiges Signal handelt oder wenn es sich um ein nicht abfangbares Signal handelt (z.B.SIGKILL),RuntimeError, wenn diese spezifische Event-Loop-Instanz keine Signale behandeln kann (da Signale pro Prozess global sind, kann nur eine mit dem Hauptthread verknüpfte Event-Loop Signale behandeln).remove_signal_handler(sig). Entfernt den Handler für das Signalsig, falls einer gesetzt ist. Löst dieselben Ausnahmen aus wieadd_signal_handler()(außer dass sieFalsezurückgeben kann, anstattRuntimeErrorfür nicht abfangbare Signale auszulösen). GibtTruezurück, wenn ein Handler erfolgreich entfernt wurde,False, wenn kein Handler gesetzt war.
Hinweis: Wenn diese Methoden statisch als nicht unterstützt bekannt sind, können sie stattdessen NotImplementedError anstelle von RuntimeError auslösen.
Gegenseitiger Ausschluss von Callbacks
Eine Event-Loop sollte die gegenseitige Ausschließung von Callbacks erzwingen, d.h. sie sollte niemals einen Callback starten, während ein vorheriger Callback noch läuft. Dies sollte für alle Arten von Callbacks gelten, unabhängig davon, ob sie mit call_soon(), call_later(), call_at(), call_soon_threadsafe(), add_reader(), add_writer() oder add_signal_handler() geplant wurden.
Ausnahmen
Es gibt zwei Kategorien von Ausnahmen in Python: solche, die von der Klasse Exception abgeleitet sind, und solche, die von BaseException abgeleitet sind. Von Exception abgeleitete Ausnahmen werden im Allgemeinen abgefangen und ordnungsgemäß behandelt; zum Beispiel werden sie von Futures weitergegeben und protokolliert und ignoriert, wenn sie in einem Callback auftreten.
Ausnahmen, die nur von BaseException abgeleitet sind, werden jedoch typischerweise nicht abgefangen und führen normalerweise zum Abbruch des Programms mit einem Traceback. In einigen Fällen werden sie abgefangen und erneut ausgelöst. (Beispiele für diese Kategorie sind KeyboardInterrupt und SystemExit; es ist in der Regel unklug, diese wie die meisten anderen Ausnahmen zu behandeln.)
Die Event-Loop übergibt die letztere Kategorie an ihren *Exception-Handler*. Dies ist ein Callback, der ein *Kontext*-Dictionary als Parameter akzeptiert.
def exception_handler(context):
...
*Kontext* kann viele verschiedene Schlüssel haben, aber einige davon werden sehr häufig verwendet.
'message': Fehlermeldung.'exception': Ausnahmeninstanz;None, wenn keine Ausnahme vorliegt.'source_traceback': Eine Liste von Zeichenketten, die den Stack an der Stelle repräsentieren, an der das am Fehler beteiligte Objekt erstellt wurde.'handle_traceback': Eine Liste von Zeichenketten, die den Stack zum Zeitpunkt der Erstellung des am Fehler beteiligten Handles repräsentieren.
Die Schleife hat die folgenden Methoden in Bezug auf die Ausnahmebehandlung.
get_exception_handler()gibt den aktuellen Exception-Handler zurück, der für die Schleife registriert ist.set_exception_handler(handler)setzt den Exception-Handler.default_exception_handler(context)der *Standard*-Exception-Handler für diese Schleifenimplementierung.call_exception_handler(context)übergibt *Kontext* an den registrierten Exception-Handler. Dies ermöglicht die einheitliche Behandlung von unbehandelten Ausnahmen durch Drittanbieterbibliotheken.Die Schleife verwendet
default_exception_handler(), wenn der Standard nicht durch einen expliziten Aufruf vonset_exception_handler()überschrieben wurde.
Debug-Modus
Standardmäßig arbeitet die Schleife im *Release*-Modus. Anwendungen können den *Debug*-Modus für eine bessere Fehlerberichterstattung auf Kosten einiger Leistung aktivieren.
Im Debug-Modus sind viele zusätzliche Prüfungen aktiviert, zum Beispiel:
- Source-Tracebacks sind für unbehandelte Ausnahmen in Futures/Tasks verfügbar.
- Die Schleife prüft auf langsame Callbacks, um versehentliche Blockaden für I/O zu erkennen.
Das Attribut
loop.slow_callback_durationsteuert die maximale Ausführungszeit zwischen zwei *Yield-Punkten*, bevor ein langsamer Callback gemeldet wird. Der Standardwert ist 0,1 Sekunden; er kann durch Zuweisung geändert werden.
Es gibt zwei Methoden im Zusammenhang mit dem Debug-Modus.
get_debug()gibtTruezurück, wenn der *Debug*-Modus aktiviert ist, andernfallsFalse.set_debug(enabled)aktiviert den *Debug*-Modus, wenn das ArgumentTrueist.
Der Debug-Modus wird automatisch aktiviert, wenn die Umgebungsvariable PYTHONASYNCIODEBUG definiert und nicht leer ist.
Handles
Die verschiedenen Methoden zum Registrieren von einmaligen Callbacks (call_soon(), call_later(), call_at() und call_soon_threadsafe()) geben alle ein Objekt zurück, das die Registrierung repräsentiert und zum Abbrechen des Callbacks verwendet werden kann. Dieses Objekt wird als Handle bezeichnet. Handles sind undurchsichtig und haben nur eine öffentliche Methode.
cancel(): Bricht den Callback ab.
Beachten Sie, dass add_reader(), add_writer() und add_signal_handler() keine Handles zurückgeben.
Server
Die Methode create_server() gibt eine Server-Instanz zurück, die die Sockets (oder andere Netzwerkobjekte) umschließt, die zum Akzeptieren von Anfragen verwendet werden. Diese Klasse hat zwei öffentliche Methoden.
close(): Schließt den Dienst. Dies stoppt die Annahme neuer Anfragen, bricht aber keine bereits angenommenen und gerade bearbeiteten Anfragen ab.wait_closed(): Eine Coroutine, die blockiert, bis der Dienst geschlossen ist und alle angenommenen Anfragen bearbeitet wurden.
Futures
Die Klasse asyncio.Future ist hier bewusst der Klasse concurrent.futures.Future ähnlich, die in PEP 3148 spezifiziert ist, aber es gibt geringfügige Unterschiede. Immer wenn dieser PEP über Futures oder futures spricht, sollte dies als Verweis auf asyncio.Future verstanden werden, es sei denn, concurrent.futures.Future wird ausdrücklich erwähnt. Die unterstützte öffentliche API ist wie folgt, wobei die Unterschiede zu PEP 3148 angegeben sind.
cancel(). Wenn das Future bereits abgeschlossen (oder abgebrochen) ist, tun Sie nichts und geben SieFalsezurück. Andernfalls wird versucht, das Future abzubrechen undTruezurückzugeben. Wenn der Abbruchversuch erfolgreich ist, wird der Zustand des Futures schließlich auf abgebrochen geändert (so dasscancelled()Truezurückgibt) und die Callbacks werden geplant. Bei regulären Futures gelingt der Abbruch immer sofort; bei Tasks (siehe unten) kann die Task den Abbruchversuch ignorieren oder verzögern.cancelled(). GibtTruezurück, wenn das Future erfolgreich abgebrochen wurde.done(). GibtTruezurück, wenn das Future abgeschlossen ist. Beachten Sie, dass ein abgebrochenes Future ebenfalls als abgeschlossen gilt (hier und überall).result(). Gibt das mitset_result()gesetzte Ergebnis zurück oder löst die mitset_exception()gesetzte Ausnahme aus. LöstCancelledErroraus, wenn abgebrochen. Unterschied zu PEP 3148: Dies hat kein Timeout-Argument und wartet *nicht*; wenn das Future noch nicht abgeschlossen ist, löst es eine Ausnahme aus.exception(). Gibt die Ausnahme zurück, wenn sie mitset_exception()gesetzt wurde, oderNone, wenn ein Ergebnis mitset_result()gesetzt wurde. LöstCancelledErroraus, wenn abgebrochen. Unterschied zu PEP 3148: Dies hat kein Timeout-Argument und wartet *nicht*; wenn das Future noch nicht abgeschlossen ist, löst es eine Ausnahme aus.add_done_callback(fn). Fügt einen Callback hinzu, der aufgerufen werden soll, wenn das Future abgeschlossen (oder abgebrochen) ist. Wenn das Future bereits abgeschlossen (oder abgebrochen) ist, wird der Callback mithilfe voncall_soon()geplant. Unterschied zu PEP 3148: Der Callback wird niemals sofort und immer im Kontext des Aufrufers aufgerufen – typischerweise ist dies ein Thread. Sie können sich das so vorstellen, als würde der Callback übercall_soon()aufgerufen. Beachten Sie, dass der Callback (im Gegensatz zu allen anderen in diesem PEP definierten Callbacks und unter Ignorierung der Konvention aus dem Abschnitt "Callback-Stil" unten), um PEP 3148 zu entsprechen, immer mit einem einzigen Argument, dem Future-Objekt, aufgerufen wird. (Die Motivation zur strengen Serialisierung von mitcall_soon()geplanten Callbacks gilt auch hier.)remove_done_callback(fn). Entfernt das Argument aus der Liste der Callbacks. Diese Methode ist nicht durch PEP 3148 definiert. Das Argument muss gleich (mit==) dem Argument sein, das anadd_done_callback()übergeben wurde. Gibt die Anzahl der Entfernungen des Callbacks zurück.set_result(result). Das Future darf noch nicht abgeschlossen (oder abgebrochen) sein. Dies macht das Future abgeschlossen und plant die Callbacks. Unterschied zu PEP 3148: Dies ist eine öffentliche API.set_exception(exception). Das Future darf noch nicht abgeschlossen (oder abgebrochen) sein. Dies macht das Future abgeschlossen und plant die Callbacks. Unterschied zu PEP 3148: Dies ist eine öffentliche API.
Die interne Methode set_running_or_notify_cancel() wird nicht unterstützt; es gibt keine Möglichkeit, den laufenden Zustand festzulegen. Ebenso wird die Methode running() nicht unterstützt.
Die folgenden Ausnahmen sind definiert.
InvalidStateError. Wird immer dann ausgelöst, wenn das Future nicht in einem für die aufgerufene Methode akzeptablen Zustand ist (z.B. Aufruf vonset_result()auf einem bereits abgeschlossenen Future oder Aufruf vonresult()auf einem noch nicht abgeschlossenen Future).InvalidTimeoutError. Wird vonresult()undexception()ausgelöst, wenn ein nicht-nulltimeout-Argument angegeben wird.CancelledError. Ein Alias fürconcurrent.futures.CancelledError. Wird ausgelöst, wennresult()oderexception()auf einem abgebrochenen Future aufgerufen wird.TimeoutError. Ein Alias fürconcurrent.futures.TimeoutError. Kann vonrun_until_complete()ausgelöst werden.
Ein Future ist mit einer Event-Loop verknüpft, wenn es erstellt wird.
Ein asyncio.Future-Objekt ist für die Funktionen wait() und as_completed() im Paket concurrent.futures nicht akzeptabel. Es gibt jedoch ähnliche APIs asyncio.wait() und asyncio.as_completed(), die nachfolgend beschrieben werden.
Ein asyncio.Future-Objekt ist für einen yield from-Ausdruck akzeptabel, wenn es in einer Coroutine verwendet wird. Dies wird über die __iter__()-Schnittstelle des Futures implementiert. Siehe den Abschnitt "Coroutinen und der Scheduler" unten.
Wenn ein Future vom Garbage Collector eingesammelt wird, und es eine zugeordnete Ausnahme hat, aber weder result() noch exception() jemals aufgerufen wurde, wird die Ausnahme protokolliert. (Wenn eine Coroutine yield from verwendet, um auf ein Future zu warten, wird die result()-Methode dieses Futures aufgerufen, sobald die Coroutine wieder aufgenommen wird.)
In Zukunft (Wortspiel beabsichtigt) könnten wir asyncio.Future und concurrent.futures.Future vereinheitlichen, z.B. durch Hinzufügen einer __iter__()-Methode zum letzteren, die mit yield from funktioniert. Um zu verhindern, dass die Event-Loop versehentlich durch den Aufruf von z.B. result() auf einem noch nicht abgeschlossenen Future blockiert wird, kann die blockierende Operation erkennen, dass in dem aktuellen Thread eine Event-Loop aktiv ist und stattdessen eine Ausnahme auslösen. Die aktuelle PEP strebt jedoch an, keine Abhängigkeiten über Python 3.3 hinaus zu haben, daher sind Änderungen an concurrent.futures.Future vorerst ausgeschlossen.
Es gibt einige öffentliche Funktionen im Zusammenhang mit Futures.
asyncio.async(arg). Dies nimmt ein Argument entgegen, das entweder ein Coroutine-Objekt oder ein Future ist (d.h. alles, was Sie mityield fromverwenden können) und gibt ein Future zurück. Wenn das Argument ein Future ist, wird es unverändert zurückgegeben; wenn es ein Coroutine-Objekt ist, wird es in eine Task (denken Sie daran, dassTaskeine Unterklasse vonFutureist) eingepackt.asyncio.wrap_future(future). Dies nimmt ein PEP 3148 Future (d.h. eine Instanz vonconcurrent.futures.Future) und gibt ein mit der Event-Loop kompatibles Future zurück (d.h. eineasyncio.Future-Instanz).
Transports
Transporte und Protokolle sind stark von Twisted und PEP 353 beeinflusst. Benutzer implementieren oder instanziieren selten Transporte – stattdessen bieten Event-Loops Hilfsmethoden zum Einrichten von Transporten.
Transporte arbeiten mit Protokollen zusammen. Protokolle werden typischerweise geschrieben, ohne das genaue Protokoll des verwendeten Transports zu kennen oder sich darum zu kümmern, und Transporte können mit einer Vielzahl von Protokollen verwendet werden. Zum Beispiel kann eine HTTP-Client-Protokollimplementierung entweder mit einem einfachen Socket-Transport oder einem SSL/TLS-Transport verwendet werden. Der einfache Socket-Transport kann neben HTTP (z.B. SMTP, IMAP, POP, FTP, IRC, SPDY) mit vielen verschiedenen Protokollen verwendet werden.
Der häufigste Transporttyp ist ein bidirektionaler Stream-Transport. Es gibt auch unidirektionale Stream-Transporte (verwendet für Pipes) und Datagramm-Transporte (verwendet von der Methode create_datagram_endpoint()).
Methoden für alle Transports
get_extra_info(name, default=None). Dies ist eine Catch-all-Methode, die implementierungsspezifische Informationen über einen Transport zurückgibt. Das erste Argument ist der Name des abzurufenden zusätzlichen Feldes. Das optionale zweite Argument ist ein Standardwert, der zurückgegeben werden soll. Konsultieren Sie die Implementierungsdokumentation, um die unterstützten Namen von zusätzlichen Feldern zu erfahren. Für einen nicht unterstützten Namen wird immer der Standardwert zurückgegeben.
Bidirektionale Stream-Transports
Ein bidirektionaler Stream-Transport ist eine Abstraktion über einem Socket oder etwas Ähnlichem (z.B. ein Paar von UNIX-Pipes oder eine SSL/TLS-Verbindung).
Die meisten Verbindungen haben eine asymmetrische Natur: der Client und der Server haben normalerweise sehr unterschiedliche Rollen und Verhaltensweisen. Daher ist die Schnittstelle zwischen Transport und Protokoll ebenfalls asymmetrisch. Aus Sicht des Protokolls erfolgt das *Schreiben* von Daten durch Aufrufen der Methode write() auf dem Transportobjekt; dies puffert die Daten und kehrt sofort zurück. Der Transport spielt jedoch eine aktivere Rolle beim *Lesen* von Daten: Immer wenn Daten vom Socket (oder einer anderen Datenquelle) gelesen werden, ruft der Transport die Methode data_received() des Protokolls auf.
Nichtsdestotrotz ist die Schnittstelle zwischen Transport und Protokoll, die von bidirektionalen Streams verwendet wird, sowohl für Clients als auch für Server dieselbe, da die Verbindung zwischen einem Client und einem Server im Wesentlichen ein Paar von Streams ist, einer in jeder Richtung.
Bidirektionale Stream-Transporte haben die folgenden öffentlichen Methoden.
write(data). Schreibt einige Bytes. Das Argument muss ein Bytes-Objekt sein. GibtNonezurück. Der Transport kann die Bytes puffern, muss aber schließlich dafür sorgen, dass die Bytes an die Entität am anderen Ende übertragen werden, und muss das Stream-Verhalten beibehalten. Das heißt,t.write(b'abc'); t.write(b'def')ist äquivalent zut.write(b'abcdef'), sowie zut.write(b'a') t.write(b'b') t.write(b'c') t.write(b'd') t.write(b'e') t.write(b'f')
writelines(iterable). Äquivalent zu.for data in iterable: self.write(data)
write_eof(). Schließt das Schreibende der Verbindung. Nachfolgende Aufrufe vonwrite()sind nicht erlaubt. Sobald alle gepufferten Daten übertragen sind, signalisiert der Transport dem anderen Ende, dass keine weiteren Daten empfangen werden. Einige Protokolle unterstützen diese Operation nicht; in diesem Fall löst der Aufruf vonwrite_eof()eine Ausnahme aus. (Hinweis: Früher hieß diese Methodehalf_close(), aber wenn Sie nicht bereits wissen, wozu sie dient, gibt dieser Name nicht an, *welches* Ende geschlossen wird.)can_write_eof(). GibtTruezurück, wenn das Protokollwrite_eof()unterstützt,False, wenn nicht. (Diese Methode gibt typischerweise einen festen Wert zurück, der nur von der spezifischen Transportklasse abhängt und nicht vom Zustand des Transportobjekts. Sie ist erforderlich, da einige Protokolle ihr Verhalten ändern müssen, wennwrite_eof()nicht verfügbar ist. Zum Beispiel wird im HTTP, um Daten zu senden, deren Größe im Voraus nicht bekannt ist, das Ende der Daten typischerweise mitwrite_eof()angezeigt; SSL/TLS unterstützt dies jedoch nicht, und eine HTTP-Protokollimplementierung müsste in diesem Fall die "Chunked" Transfer-Codierung verwenden. Wenn die Datengröße jedoch im Voraus bekannt ist, ist der beste Ansatz in beiden Fällen die Verwendung des Content-Length-Headers.)get_write_buffer_size(). Gibt die aktuelle Größe des Schreibpuffers des Transports in Bytes zurück. Dies berücksichtigt nur den explizit vom Transport verwalteten Schreibpuffer; Pufferung in anderen Schichten des Netzwerkstapels oder anderswo im Netzwerk wird nicht gemeldet.set_write_buffer_limits(high=None, low=None). Legt die oberen und unteren Grenzen für die Flusskontrolle fest.Diese beiden Werte steuern, wann die Methoden
pause_writing()undresume_writing()des Protokolls aufgerufen werden. Wenn angegeben, muss die untere Grenze kleiner oder gleich der oberen Grenze sein. Kein Wert darf negativ sein.Die Standardwerte sind implementierungsspezifisch. Wenn nur die obere Grenze angegeben ist, wird die untere Grenze auf einen implementierungsspezifischen Wert kleiner oder gleich der oberen Grenze gesetzt. Das Setzen von High auf Null zwingt auch Low auf Null und bewirkt, dass
pause_writing()aufgerufen wird, sobald der Puffer nicht leer ist. Das Setzen von Low auf Null bewirkt, dassresume_writing()erst aufgerufen wird, wenn der Puffer leer ist. Die Verwendung von Null für eine der Grenzen ist im Allgemeinen sub-optimal, da sie die Möglichkeiten zur parallelen Ausführung von I/O und Berechnungen einschränkt.pause_reading(). Hält die Datenlieferung an das Protokoll an, bis ein nachfolgender Aufruf vonresume_reading()erfolgt. Zwischenpause_reading()undresume_reading()wird die Methodedata_received()des Protokolls nicht aufgerufen.resume_reading(). Setzt die Datenlieferung an das Protokoll überdata_received()fort. Beachten Sie, dass "pausiert" ein binärer Zustand ist –pause_reading()sollte nur aufgerufen werden, wenn der Transport nicht pausiert ist, währendresume_reading()nur aufgerufen werden sollte, wenn der Transport pausiert ist.close(). Trennt die Verbindung zur Entität am anderen Ende. Alle vonwrite()gepufferten Daten werden (schließlich) übertragen, bevor die Verbindung tatsächlich geschlossen wird. Die Methodedata_received()des Protokolls wird nicht erneut aufgerufen. Sobald alle gepufferten Daten geleert sind, wird die Methodeconnection_lost()des Protokolls mitNoneals Argument aufgerufen. Beachten Sie, dass diese Methode nicht darauf wartet, dass all dies geschieht.abort(). Trennt die Verbindung sofort. Alle noch vom Transport gepufferten Daten werden verworfen. Bald darauf wird die Methodeconnection_lost()des Protokolls mitNoneals Argument aufgerufen.
Unidirektionale Stream-Transports
Ein Schreib-Stream-Transport unterstützt die Methoden write(), writelines(), write_eof(), can_write_eof(), close() und abort(), die für bidirektionale Stream-Transporte beschrieben wurden.
Ein Lese-Stream-Transport unterstützt die Methoden pause_reading(), resume_reading() und close(), die für bidirektionale Stream-Transporte beschrieben wurden.
Ein Schreib-Stream-Transport ruft nur connection_made() und connection_lost() auf seinem zugehörigen Protokoll auf.
Ein Lese-Stream-Transport kann alle in der folgenden Abschnitte „Protokolle“ aufgeführten Protokollmethoden aufrufen (d. h. die beiden vorherigen plus data_received() und eof_received()).
Datagramm-Transports
Datagramm-Transporte haben diese Methoden
sendto(data, addr=None). Sendet ein Datagramm (ein Bytes-Objekt). Das optionale zweite Argument ist die Zieladresse. Wenn es weggelassen wird, mussremote_addrim Aufrufcreate_datagram_endpoint(), der diesen Transport erstellt hat, angegeben worden sein. Wenn es vorhanden ist undremote_addrangegeben wurde, müssen sie übereinstimmen. Das (data, addr)-Paar kann sofort gesendet oder gepuffert werden. Der Rückgabewert istNone.abort(). Schließt den Transport sofort. Gepufferte Daten werden verworfen.close(). Schließt den Transport. Gepufferte Daten werden asynchron übertragen.
Datagramm-Transporte rufen die folgenden Methoden auf dem zugehörigen Protokoll-Objekt auf: connection_made(), connection_lost(), error_received() und datagram_received(). („Connection“ in diesen Methodennamen ist ein kleiner Fehlbegriff, aber die Konzepte existieren weiterhin: connection_made() bedeutet, dass der die Endpunkt repräsentierende Transport erstellt wurde, und connection_lost() bedeutet, dass der Transport geschlossen ist.)
Subprozess-Transports
Subprozess-Transporte haben die folgenden Methoden
get_pid(). Gibt die Prozess-ID des Subprozesses zurück.get_returncode(). Gibt den Rückgabecode des Prozesses zurück, wenn der Prozess beendet wurde; andernfallsNone.get_pipe_transport(fd). Gibt den Pipe-Transport (einen unidirektionalen Stream-Transport) zurück, der dem Argument entspricht, das 0, 1 oder 2 für stdin, stdout oder stderr (des Subprozesses) darstellen sollte. Wenn kein solcher Pipe-Transport vorhanden ist, wirdNonezurückgegeben. Für stdin ist dies ein Schreibtransport; für stdout und stderr ist dies ein Lesetransport. Sie müssen diese Methode verwenden, um einen Transport zu erhalten, den Sie zum Schreiben in stdin des Subprozesses verwenden können.send_signal(signal). Sendet ein Signal an den Subprozess.terminate(). Beendet den Subprozess.kill(). Beendet den Subprozess mit Gewalt. Unter Windows ist dies ein Alias fürterminate().close(). Dies ist ein Alias fürterminate().
Beachten Sie, dass send_signal(), terminate() und kill() die entsprechenden Methoden des Moduls subprocess der Standardbibliothek umschließen.
Protokolle
Protokolle werden immer in Verbindung mit Transports verwendet. Während einige gängige Protokolle bereitgestellt werden (z. B. anständige, wenn auch nicht unbedingt ausgezeichnete HTTP-Client- und Serverimplementierungen), werden die meisten Protokolle vom Benutzercode oder von Drittanbieterbibliotheken implementiert.
Wie bei Transports unterscheiden wir zwischen Stream-Protokollen, Datagramm-Protokollen und möglicherweise anderen benutzerdefinierten Protokollen. Der gebräuchlichste Protokolltyp ist ein bidirektionales Stream-Protokoll. (Es gibt keine unidirektionalen Protokolle.)
Stream-Protokolle
Ein (bidirektionales) Stream-Protokoll muss die folgenden Methoden implementieren, die vom Transport aufgerufen werden. Betrachten Sie diese als Rückrufe, die immer vom Event-Loop im richtigen Kontext aufgerufen werden. (Siehe den Abschnitt „Kontext“ weiter oben.)
connection_made(transport). Zeigt an, dass der Transport bereit und mit der Entität am anderen Ende verbunden ist. Das Protokoll sollte wahrscheinlich die Transportreferenz als Instanzvariable speichern (damit es später seinewrite()- und andere Methoden aufrufen kann) und kann an dieser Stelle eine anfängliche Begrüßung oder Anfrage schreiben.data_received(data). Der Transport hat einige Bytes von der Verbindung gelesen. Das Argument ist immer ein nicht leerer Bytes-Objekt. Es gibt keine Garantie für die minimale oder maximale Größe der auf diese Weise übergebenen Daten.p.data_received(b'abcdef')sollte genauso behandelt werden wiep.data_received(b'abc') p.data_received(b'def')
eof_received(). Dies wird aufgerufen, wenn das andere Endewrite_eof()(oder etwas Äquivalentes) aufgerufen hat. Wenn dies einen falschen Wert (einschließlichNone) zurückgibt, schließt sich der Transport selbst. Wenn er einen wahren Wert zurückgibt, liegt das Schließen des Transports beim Protokoll. Für SSL/TLS-Verbindungen wird dies jedoch ignoriert, da der TLS-Standard vorschreibt, dass keine weiteren Daten gesendet werden und die Verbindung geschlossen wird, sobald eine „Abschlusswarnung“ empfangen wird.Die Standardimplementierung gibt
Nonezurück.pause_writing(). Fordert das Protokoll auf, das Schreiben von Daten an den Transport vorübergehend einzustellen. Die Beachtung der Anforderung ist optional, aber der Puffer des Transports kann ohne Grenzen wachsen, wenn Sie weiter schreiben. Die Puffergröße, bei der dies aufgerufen wird, kann über die Methodeset_write_buffer_limits()des Transports gesteuert werden.resume_writing(). Teilt dem Protokoll mit, dass es sicher ist, wieder Daten an den Transport zu schreiben. Beachten Sie, dass dies direkt von derwrite()-Methode des Transports aufgerufen werden kann (im Gegensatz zum indirekten Aufruf übercall_soon()), sodass sich das Protokoll seines pausierten Zustands unmittelbar nach Rückgabe vonwrite()bewusst sein kann.connection_lost(exc). Der Transport wurde geschlossen oder abgebrochen, hat erkannt, dass das andere Ende die Verbindung sauber geschlossen hat oder auf einen unerwarteten Fehler gestoßen ist. In den ersten drei Fällen ist das ArgumentNone; bei einem unerwarteten Fehler ist das Argument die Ausnahme, die dazu geführt hat, dass der Transport aufgegeben hat.
Hier ist eine Tabelle, die die Reihenfolge und Anzahl der grundlegenden Aufrufe angibt
connection_made()– genau einmaldata_received()– null oder mehrfacheof_received()– höchstens einmalconnection_lost()– genau einmal
Aufrufe von pause_writing() und resume_writing() erfolgen paarweise und nur zwischen #1 und #4. Diese Paare werden nicht verschachtelt. Der letzte Aufruf von resume_writing() kann weggelassen werden; d. h. eine pausierte Verbindung kann verloren gehen und nie wieder aufgenommen werden.
Datagramm-Protokolle
Datagramm-Protokolle haben die Methoden connection_made() und connection_lost() mit denselben Signaturen wie Stream-Protokolle. (Wie im Abschnitt über Datagramm-Transporte erläutert, bevorzugen wir die leicht ungewöhnliche Nomenklatur gegenüber der Definition unterschiedlicher Methodennamen, um die Öffnung und Schließung des Sockets anzuzeigen.)
Zusätzlich haben sie die folgenden Methoden
datagram_received(data, addr). Zeigt an, dass ein Datagrammdata(ein Bytes-Objekt) von der Remote-Adresseaddr(ein IPv4 2-Tupel oder ein IPv6 4-Tupel) empfangen wurde.error_received(exc). Zeigt an, dass eine Sende- oder Empfangsoperation eineOSError-Ausnahme ausgelöst hat. Da Datagramm-Fehler vorübergehend sein können, liegt es am Protokoll, die Methodeclose()des Transports aufzurufen, wenn die Endpunkt geschlossen werden soll.
Hier ist eine Tabelle, die die Reihenfolge und Anzahl der Aufrufe angibt
connection_made()– genau einmaldatagram_received(),error_received()– null oder mehrfachconnection_lost()– genau einmal
Subprozess-Protokoll
Subprozess-Protokolle haben die Methoden connection_made(), connection_lost(), pause_writing() und resume_writing() mit denselben Signaturen wie Stream-Protokolle. Zusätzlich haben sie die folgenden Methoden
pipe_data_received(fd, data). Wird aufgerufen, wenn der Subprozess Daten auf stdout oder stderr schreibt.fdist der Dateideskriptor (1 für stdout, 2 für stderr).dataist einbytes-Objekt.pipe_connection_lost(fd, exc). Wird aufgerufen, wenn der Subprozess stdin, stdout oder stderr schließt.fdist der Dateideskriptor.excist eine Ausnahme oderNone.process_exited(). Wird aufgerufen, wenn der Subprozess beendet wurde. Um den Exit-Status abzurufen, verwenden Sie die Methodeget_returncode()des Transports.
Beachten Sie, dass je nach Verhalten des Subprozesses process_exited() entweder vor oder nach pipe_connection_lost() aufgerufen werden kann. Wenn beispielsweise der Subprozess einen Sub-Subprozess erstellt, der dessen stdin/stdout/stderr teilt, und sich dann selbst beendet, kann process_exited() aufgerufen werden, während alle Pipes noch geöffnet sind. Wenn andererseits der Subprozess seine stdin/stdout/stderr schließt, sich aber nicht beendet, kann pipe_connection_lost() für alle drei Pipes aufgerufen werden, ohne dass process_exited() aufgerufen wird. Wenn (wie in den meisten Fällen) der Subprozess beendet wird und dadurch implizit alle Pipes schließt, ist die Aufruf-Reihenfolge undefiniert.
Callback-Stil
Die meisten Schnittstellen, die einen Callback entgegennehmen, nehmen auch Positionsargumente entgegen. Um beispielsweise zu arrangieren, dass foo("abc", 42) bald aufgerufen wird, rufen Sie loop.call_soon(foo, "abc", 42) auf. Um den Aufruf foo() zu planen, verwenden Sie loop.call_soon(foo). Diese Konvention reduziert die Anzahl kleiner Lambdas, die in der typischen Callback-Programmierung benötigt werden, erheblich.
Diese Konvention unterstützt ausdrücklich keine Schlüsselwortargumente. Schlüsselwortargumente werden verwendet, um optional zusätzliche Informationen über den Callback zu übergeben. Dies ermöglicht eine reibungslose Weiterentwicklung der API, ohne sich Gedanken darüber machen zu müssen, ob ein Schlüsselwort für einen Aufgerufenen irgendwo von Bedeutung sein könnte. Wenn Sie einen Callback haben, der *unbedingt* mit einem Schlüsselwortargument aufgerufen werden muss, können Sie eine Lambda-Funktion verwenden. Zum Beispiel
loop.call_soon(lambda: foo('abc', repeat=42))
Coroutinen und der Scheduler
Dies ist ein separater Oberabschnitt, da sein Status sich vom Interface der Event-Schleife unterscheidet. Die Verwendung von Coroutinen ist optional, und es ist völlig in Ordnung, Code nur mit Callbacks zu schreiben. Andererseits gibt es nur eine Implementierung der Scheduler-/Coroutine-API, und wenn Sie Coroutinen verwenden, ist es diese, die Sie verwenden.
Coroutinen
Eine Coroutine ist ein Generator, der bestimmten Konventionen folgt. Zu Dokumentationszwecken sollten alle Coroutinen mit @asyncio.coroutine dekoriert werden, dies kann jedoch nicht strikt erzwungen werden.
Coroutinen verwenden die yield from-Syntax, die in PEP 380 eingeführt wurde, anstelle der ursprünglichen yield-Syntax.
Das Wort „coroutine“ (Coroutine), wie das Wort „generator“ (Generator), wird für zwei unterschiedliche (wenn auch verwandte) Konzepte verwendet
- Die Funktion, die eine Coroutine definiert (eine mit
asyncio.coroutinedekorierte Funktionsdefinition). Wenn eine Unterscheidung erforderlich ist, nennen wir dies eine Coroutine-Funktion. - Das Objekt, das durch Aufrufen einer Coroutine-Funktion erhalten wird. Dieses Objekt repräsentiert eine Berechnung oder eine I/O-Operation (normalerweise eine Kombination), die schließlich abgeschlossen wird. Wenn eine Unterscheidung erforderlich ist, nennen wir es ein Coroutine-Objekt.
Dinge, die eine Coroutine tun kann
result = yield from future– suspendiert die Coroutine, bis die Future abgeschlossen ist, gibt dann das Ergebnis der Future zurück oder löst eine Ausnahme aus, die weitergegeben wird. (Wenn die Future abgebrochen wird, löst sie eineCancelledError-Ausnahme aus.) Beachten Sie, dass Tasks Futures sind und alles, was über Futures gesagt wird, auch für Tasks gilt.result = yield from coroutine– wartet darauf, dass eine andere Coroutine ein Ergebnis liefert (oder eine Ausnahme auslöst, die weitergegeben wird). Der Ausdruckcoroutinemuss ein Aufruf einer anderen Coroutine sein.return expression– liefert ein Ergebnis an die Coroutine, die mityield fromauf diese wartet.raise exception– löst eine Ausnahme in der Coroutine aus, die mityield fromauf diese wartet.
Das Aufrufen einer Coroutine startet ihren Code nicht – es ist nur ein Generator, und das von dem Aufruf zurückgegebene Coroutine-Objekt ist eigentlich ein Generator-Objekt, das nichts tut, bis Sie es durchlaufen. Im Fall eines Coroutine-Objekts gibt es zwei grundlegende Möglichkeiten, es auszuführen: Rufen Sie yield from coroutine aus einer anderen Coroutine auf (vorausgesetzt, die andere Coroutine läuft bereits!) oder konvertieren Sie es in einen Task (siehe unten).
Coroutinen (und Tasks) können nur ausgeführt werden, wenn die Event-Schleife läuft.
Auf mehrere Coroutinen warten
Um auf mehrere Coroutinen oder Futures zu warten, werden zwei APIs bereitgestellt, die den APIs wait() und as_completed() im Paket concurrent.futures ähneln.
asyncio.wait(fs, timeout=None, return_when=ALL_COMPLETED). Dies ist eine Coroutine, die auf die Futures oder Coroutinen wartet, die vonfsbereitgestellt werden, bis sie abgeschlossen sind. Coroutine-Argumente werden in Tasks (siehe unten) verpackt. Dies gibt eine Future zurück, deren Ergebnis bei Erfolg ein Tupel aus zwei Mengen von Futures ist,(done, pending), wobeidonedie Menge der ursprünglichen Futures (oder verpackten Coroutinen) ist, die abgeschlossen (oder abgebrochen) sind, undpendingder Rest ist, d. h. diejenigen, die noch nicht abgeschlossen (noch nicht abgebrochen) sind. Beachten Sie, dass mit den Standardwerten fürtimeoutundreturn_whendoneimmer eine leere Liste sein wird. Optionale Argumentetimeoutundreturn_whenhaben dieselbe Bedeutung und Standardwerte wie fürconcurrent.futures.wait():timeoutgibt, wenn nichtNone, ein Timeout für die Gesamtoperation an;return_whengibt an, wann gestoppt werden soll. Die KonstantenFIRST_COMPLETED,FIRST_EXCEPTION,ALL_COMPLETEDsind mit denselben Werten und derselben Bedeutung wie in PEP 3148 definiert.ALL_COMPLETED(Standard): Warten, bis alle Futures abgeschlossen sind (oder bis das Timeout eintritt).FIRST_COMPLETED: Warten, bis mindestens eine Future abgeschlossen ist (oder bis das Timeout eintritt).FIRST_EXCEPTION: Warten, bis mindestens eine Future abgeschlossen ist, aber nicht mit einer Ausnahme abgebrochen wurde. (Der Ausschluss abgebrochener Futures von der Bedingung ist überraschend, aber PEP 3148 macht es auf diese Weise.)
asyncio.as_completed(fs, timeout=None). Gibt einen Iterator zurück, dessen Werte Futures oder Coroutinen sind; das Warten auf aufeinanderfolgende Werte wartet, bis die nächste Future oder Coroutine aus der Mengefsabgeschlossen ist, und gibt ihr Ergebnis zurück (oder löst ihre Ausnahme aus). Das optionale Argumenttimeouthat dieselbe Bedeutung und denselben Standardwert wie fürconcurrent.futures.wait(): Wenn das Timeout eintritt, löst die vom Iterator zurückgegebene nächste FutureTimeoutErroraus, wenn darauf gewartet wird. Beispiel für die Verwendungfor f in as_completed(fs): result = yield from f # May raise an exception. # Use result.
Hinweis: Wenn Sie nicht auf die vom Iterator produzierten Werte warten, kommt Ihre
for-Schleife möglicherweise nicht voran (da Sie anderen Tasks nicht erlauben, zu laufen).asyncio.wait_for(f, timeout). Dies ist eine praktische Funktion, um mit einem Timeout auf eine einzelne Coroutine oder Future zu warten. Wenn ein Timeout eintritt, bricht sie den Task ab und löst TimeoutError aus. Um die Task-Abbrechung zu vermeiden, verpacken Sie sie inshield().asyncio.gather(f1, f2, ...). Gibt eine Future zurück, die wartet, bis alle Argumente (Futures oder Coroutinen) abgeschlossen sind, und gibt eine Liste ihrer entsprechenden Ergebnisse zurück. Wenn eines oder mehrere der Argumente abgebrochen wird oder eine Ausnahme auslöst, wird die zurückgegebene Future abgebrochen oder hat ihre Ausnahme gesetzt (entsprechend dem, was mit dem ersten Argument passiert ist), und die verbleibenden Argumente laufen im Hintergrund weiter. Das Abbrechen der zurückgegebenen Future hat keine Auswirkungen auf die Argumente. Beachten Sie, dass Coroutine-Argumente mitasyncio.async()in Futures konvertiert werden.asyncio.shield(f). Wartet auf eine Future und schützt sie vor Abbrechung. Dies gibt eine Future zurück, deren Ergebnis oder Ausnahme genau dieselbe ist wie die des Arguments; wenn die zurückgegebene Future jedoch abgebrochen wird, bleibt die Argument-Future unberührt.Ein Anwendungsfall für diese Funktion wäre eine Coroutine, die ein Abfrageergebnis für eine Coroutine zwischenspeichert, die eine Anfrage in einem HTTP-Server bearbeitet. Wenn die Anfrage vom Client abgebrochen wird, möchten wir (wohl) die Coroutine zur Abfragezwischenspeicherung weiterlaufen lassen, damit bei einer erneuten Verbindung des Clients das Abfrageergebnis (hoffentlich) zwischengespeichert ist. Dies könnte z. B. wie folgt geschrieben werden:
@asyncio.coroutine def handle_request(self, request): ... cached_query = self.get_cache(...) if cached_query is None: cached_query = yield from asyncio.shield(self.fill_cache(...)) ...
Schlafen
Die Coroutine asyncio.sleep(delay) kehrt nach einer gegebenen Zeitverzögerung zurück.
Tasks
Ein Task ist ein Objekt, das eine unabhängig laufende Coroutine verwaltet. Die Task-Schnittstelle ist dieselbe wie die Future-Schnittstelle, und tatsächlich ist Task eine Unterklasse von Future. Der Task wird abgeschlossen, wenn seine Coroutine zurückkehrt oder eine Ausnahme auslöst; wenn sie ein Ergebnis zurückgibt, wird dies zum Ergebnis des Tasks, wenn sie eine Ausnahme auslöst, wird dies zur Ausnahme des Tasks.
Das Abbrechen eines Tasks, der noch nicht abgeschlossen ist, wirft eine Ausnahme asyncio.CancelledError in die Coroutine. Wenn die Coroutine diese nicht abfängt (oder wenn sie sie erneut auslöst), wird der Task als abgebrochen markiert (d. h. cancelled() gibt True zurück); wenn die Coroutine die Ausnahme jedoch irgendwie abfängt und ignoriert, kann sie weiter ausgeführt werden (und cancelled() gibt False zurück).
Tasks sind auch nützlich für die Interoperabilität zwischen Coroutinen und Callback-basierten Frameworks wie Twisted. Nach der Konvertierung einer Coroutine in einen Task können Callbacks zum Task hinzugefügt werden.
Um eine Coroutine in einen Task zu konvertieren, rufen Sie die Coroutine-Funktion auf und übergeben Sie das resultierende Coroutine-Objekt an die Methode loop.create_task(). Sie können auch asyncio.ensure_future() für diesen Zweck verwenden.
Sie fragen sich vielleicht, warum nicht alle Coroutinen automatisch in Tasks konvertiert werden? Der @asyncio.coroutine-Decorator könnte dies tun. Dies würde jedoch die Leistung erheblich beeinträchtigen, wenn eine Coroutine eine andere aufruft (und so weiter), da der Wechsel zu einer „nackten“ Coroutine viel geringere Overhead hat als der Wechsel zu einem Task.
Die Klasse Task leitet sich von Future ab und fügt neue Methoden hinzu
current_task(loop=None). Eine Klassenmethode, die den aktuell laufenden Task in einer Event-Schleife zurückgibt. Wenn loopNoneist, gibt die Methode den aktuellen Task für die Standard-Schleife zurück. Jede Coroutine wird innerhalb eines Task-Kontexts ausgeführt, entweder einesTask, der mitensure_future()oderloop.create_task()erstellt wurde, oder indem er von einer anderen Coroutine überyield fromoderawaitaufgerufen wird. Diese Methode gibtNonezurück, wenn sie außerhalb einer Coroutine aufgerufen wird, z. B. in einem Callback, der überloop.call_later()geplant wurde.all_tasks(loop=None). Eine Klassenmethode, die eine Menge aller aktiven Tasks für die Schleife zurückgibt. Diese verwendet die Standard-Schleife, wenn loopNoneist.
Der Scheduler
Der Scheduler hat keine öffentliche Schnittstelle. Sie interagieren mit ihm, indem Sie yield from future und yield from task verwenden. Tatsächlich gibt es kein einzelnes Objekt, das den Scheduler repräsentiert – sein Verhalten wird von den Klassen Task und Future implementiert, die nur die öffentliche Schnittstelle der Event-Schleife verwenden, sodass er auch mit Event-Schleifen-Implementierungen von Drittanbietern funktioniert.
Nützliche Hilfsmittel
Einige Funktionen und Klassen werden bereitgestellt, um das Schreiben grundlegender Stream-basierter Clients und Server wie FTP oder HTTP zu vereinfachen. Diese sind:
asyncio.open_connection(host, port): Ein Wrapper fürEventLoop.create_connection(), der keine Angabe einerProtocol-Factory oder -Klasse erfordert. Dies ist eine Coroutine, die ein(reader, writer)-Paar zurückgibt, wobeireadereine Instanz vonStreamReaderundwritereine Instanz vonStreamWriterist (beide unten beschrieben).asyncio.start_server(client_connected_cb, host, port): Ein Wrapper fürEventLoop.create_server(), der eine einfache Callback-Funktion anstelle einerProtocol-Factory oder -Klasse entgegennimmt. Dies ist eine Coroutine, die einServer-Objekt zurückgibt, genau wiecreate_server(). Jedes Mal, wenn eine Client-Verbindung angenommen wird, wirdclient_connected_cb(reader, writer)aufgerufen, wobeireadereine Instanz vonStreamReaderundwritereine Instanz vonStreamWriterist (beide unten beschrieben). Wenn das vonclient_connected_cb()zurückgegebene Ergebnis eine Coroutine ist, wird es automatisch in einenTaskverpackt.StreamReader: Eine Klasse, die eine Schnittstelle bietet, die der eines schreibgeschützten Binärstreams nicht unähnlich ist, mit der Ausnahme, dass die verschiedenen Lesemethoden Coroutinen sind. Sie wird normalerweise von einerStreamReaderProtocol-Instanz gesteuert. Beachten Sie, dass es nur einen Reader geben sollte. Die Schnittstelle für den Reader istreadline(): Eine Coroutine, die eine Byte-Zeichenkette liest, die eine Textzeile darstellt, die mit'\n'endet, oder bis zum Ende des Streams, je nachdem, was zuerst eintritt.read(n): Eine Coroutine, die bis zunBytes liest. Wennnweggelassen wird oder negativ ist, liest sie bis zum Ende des Streams.readexactly(n): Eine Coroutine, die genaunBytes liest, oder bis zum Ende des Streams, je nachdem, was zuerst eintritt.exception(): Gibt die Ausnahme zurück, die mitset_exception()auf dem Stream gesetzt wurde, oder None, wenn keine Ausnahme gesetzt ist.
Die Schnittstelle für den Treiber ist
feed_data(data): Hängtdata(einbytes-Objekt) an den internen Puffer an. Dies entsperrt eine blockierte Lesecoroutine, wenn sie genügend Daten liefert, um die Anforderungen des Readers zu erfüllen.feed_eof(): Signalisiert das Ende des Puffers. Dies entsperrt eine blockierte Lesecoroutine. Nach diesem Aufruf sollten keine weiteren Daten an den Reader geliefert werden.set_exception(exc): Setzt eine Ausnahme auf dem Stream. Alle nachfolgenden Lesemethoden lösen diese Ausnahme aus. Nach diesem Aufruf sollten keine weiteren Daten an den Reader geliefert werden.
StreamWriter: Eine Klasse, die eine Schnittstelle bietet, die der einer schreibgeschützten Binärstreams nicht unähnlich ist. Sie wickelt einen Transport ein. Die Schnittstelle ist eine erweiterte Teilmenge der Transportschnittstelle: Die folgenden Methoden verhalten sich gleich wie die entsprechenden Transportmethoden:write(),writelines(),write_eof(),can_write_eof(),get_extra_info(),close(). Beachten Sie, dass die Schreibmethoden _keine_ Coroutinen sind (dies ist dasselbe wie bei Transports, aber anders als bei derStreamReader-Klasse). Die folgende Methode ist zusätzlich zur Transportschnittstelledrain(): Dies sollte mityield fromaufgerufen werden, nachdem signifikante Daten geschrieben wurden, zum Zweck der Flusskontrolle. Die beabsichtigte Verwendung ist wie folgtwriter.write(data) yield from writer.drain()
Beachten Sie, dass dies technisch gesehen keine Coroutine ist: Sie gibt entweder ein Future oder ein leeres Tupel zurück (beide können an
yield fromübergeben werden). Die Verwendung dieser Methode ist optional. Wenn sie jedoch nicht verwendet wird, kann der interne Puffer des Transports, der denStreamWriterzugrunde liegt, mit allen Daten gefüllt werden, die jemals in den Writer geschrieben wurden. Wenn eine Anwendung keine strenge Beschränkung hat, wie viele Daten sie schreibt, _sollte_ sie gelegentlichyield from drain()aufrufen, um eine Überfüllung des Transportpuffers zu vermeiden.
StreamReaderProtocol: Eine Protokollimplementierung, die als Adapter zwischen der bidirektionalen Stream-Transport/Protokoll-Schnittstelle und den KlassenStreamReaderundStreamWriterdient. Sie fungiert als Treiber für eine bestimmteStreamReader-Instanz und ruft deren Methodenfeed_data(),feed_eof()undset_exception()als Reaktion auf verschiedene Protokoll-Callbacks auf. Sie steuert auch das Verhalten derdrain()-Methode derStreamWriter-Instanz.
Synchronisation
Sperren, Ereignisse, Bedingungen und Semaphoren, die nach denen des threading-Moduls modelliert sind, werden implementiert und können durch Importieren des asyncio.locks-Submoduls zugegriffen werden. Warteschlangen, die nach denen des queue-Moduls modelliert sind, werden implementiert und können durch Importieren des asyncio.queues-Submoduls zugegriffen werden.
Im Allgemeinen gibt es eine enge Entsprechung zu ihren Thread-Gegenstücken. Blockierende Methoden (z. B. acquire() bei Sperren, put() und get() bei Warteschlangen) sind jedoch Coroutinen, und Zeitlimitparameter werden nicht bereitgestellt (Sie können jedoch asyncio.wait_for() verwenden, um einer blockierenden Operation ein Zeitlimit hinzuzufügen).
Die Docstrings in den Modulen bieten vollständigere Dokumentation.
Locks
Die folgenden Klassen werden von asyncio.locks bereitgestellt. Für alle außer Event kann die with-Anweisung in Kombination mit yield from verwendet werden, um die Sperre zu erwerben und sicherzustellen, dass die Sperre unabhängig davon, wie der with-Block verlassen wird, freigegeben wird, wie folgt
with (yield from my_lock):
...
Lock: ein grundlegendes Mutex mit den Methodenacquire()(eine Coroutine),locked()undrelease().Event: eine Ereignisvariable mit den Methodenwait()(eine Coroutine),set(),clear()undis_set().Condition: eine Bedingungsvariable mit den Methodenacquire(),wait(),wait_for(predicate)(alle drei Coroutinen),locked(),release(),notify()undnotify_all().Semaphore: ein Semaphor mit den Methodenacquire()(eine Coroutine),locked()undrelease(). Das Konstruktorargument ist der Anfangswert (Standard1).BoundedSemaphore: ein begrenzter Semaphor; dies ähneltSemaphore, aber der Anfangswert ist auch der Maximalwert.
Queues
Die folgenden Klassen und Ausnahmen werden von asyncio.queues bereitgestellt.
Queue: eine Standardwarteschlange mit den Methodenget(),put()(beide Coroutinen),get_nowait(),put_nowait(),empty(),full(),qsize()undmaxsize().PriorityQueue: eine Unterklasse vonQueue, die Einträge in der Reihenfolge der Priorität (niedrigste zuerst) abruft.LifoQueue: eine Unterklasse vonQueue, die die zuletzt hinzugefügten Einträge zuerst abruft.JoinableQueue: eine Unterklasse vonQueuemit den Methodentask_done()undjoin()(letztere ist eine Coroutine).Empty,Full: Ausnahmen, die ausgelöst werden, wennget_nowait()oderput_nowait()auf einer leeren bzw. vollen Warteschlange aufgerufen wird.
Verschiedenes
Logging
Alle Protokollierungen, die vom asyncio-Paket durchgeführt werden, verwenden ein einzelnes logging.Logger-Objekt, asyncio.logger. Um die Protokollierung anzupassen, können Sie die Standard-Logger-API für dieses Objekt verwenden. (Ersetzen Sie das Objekt jedoch nicht.)
Behandlung von SIGCHLD unter UNIX
Die effiziente Implementierung der process_exited()-Methode bei Subprozessprotokollen erfordert einen SIGCHLD-Signalhandler. Signalhandler können jedoch nur auf der Ereignisschleife gesetzt werden, die mit dem Hauptthread verknüpft ist. Um das Starten von Subprozessen von Ereignisschleifen aus anderen Threads zu unterstützen, gibt es einen Mechanismus, um einen SIGCHLD-Handler zwischen mehreren Ereignisschleifen zu teilen. Es gibt zwei zusätzliche Funktionen, asyncio.get_child_watcher() und asyncio.set_child_watcher(), und entsprechende Methoden auf der Ereignisschleifen-Richtlinie.
Es gibt zwei Implementierungsklassen für Child Watcher: FastChildWatcher und SafeChildWatcher. Beide verwenden SIGCHLD. Die Klasse SafeChildWatcher wird standardmäßig verwendet; sie ist ineffizient, wenn gleichzeitig viele Subprozesse existieren. Die Klasse FastChildWatcher ist effizient, kann aber mit anderem Code (entweder C-Code oder Python-Code) interferieren, der Subprozesse ohne eine asyncio-Ereignisschleife startet. Wenn Sie sicher sind, dass Sie keinen anderen Code verwenden, der Subprozesse startet, um die schnelle Implementierung zu verwenden, führen Sie Folgendes in Ihrem Hauptthread aus
watcher = asyncio.FastChildWatcher()
asyncio.set_child_watcher(watcher)
Wunschliste
(Es besteht Einigkeit darüber, dass diese Funktionen wünschenswert sind, aber keine Implementierung verfügbar war, als Python 3.4 Beta 1 veröffentlicht wurde, und der Feature-Freeze für den Rest des Python 3.4-Release-Zyklus verbietet deren Hinzufügung zu diesem späten Zeitpunkt. Sie werden jedoch hoffentlich in Python 3.5 und möglicherweise früher in der PyPI-Distribution hinzugefügt werden.)
- Unterstützt eine "Start TLS"-Operation, um einen TCP-Socket auf SSL/TLS zu aktualisieren.
Frühere Wunschlistenpunkte, die seitdem implementiert wurden (aber nicht in der PEP spezifiziert sind)
- UNIX-Domain-Sockets.
- Ein pro-Schleifen-Fehlerbehandlungs-Callback.
Offene Fragen
(Beachten Sie, dass diese de facto zugunsten des Status quo durch die Annahme der PEP gelöst wurden. Der vorläufige Status der PEP erlaubt jedoch die Überarbeitung dieser Entscheidungen für Python 3.5.)
- Warum haben
create_connection()undcreate_datagram_endpoint()einproto-Argument, abercreate_server()nicht? Und warum sind die Argumente family, flag, proto fürgetaddrinfo()manchmal Null und manchmal benannte Konstanten (deren Wert ebenfalls Null ist)? - Benötigen wir eine weitere Abfragemethode, um festzustellen, ob die Schleife gerade gestoppt wird?
- Eine vollständigere öffentliche API für Handle? Was ist der Anwendungsfall?
- Eine Debugging-API? Z. B. etwas, das viel protokolliert oder ungewöhnliche Bedingungen protokolliert (wie Warteschlangen, die schneller gefüllt werden, als sie geleert werden) oder sogar Callbacks, die zu viel Zeit in Anspruch nehmen...
- Benötigen wir Introspektions-APIs? Z. B. Abfragen des Lese-Callbacks für einen File-Deskriptor. Oder wann der nächste geplante Aufruf stattfindet. Oder die Liste der File-Deskriptoren, die mit Callbacks registriert sind. Derzeit erfordern diese alle die Verwendung interner Elemente.
- Benötigen wir weitere Socket-I/O-Methoden, z. B.
sock_sendto()undsock_recvfrom(), und vielleicht andere wiepipe_read()? Ich schätze, Benutzer können ihre eigenen schreiben (es ist keine Raketenwissenschaft). - Wir benötigen möglicherweise APIs zur Steuerung verschiedener Zeitlimits. Z. B. möchten wir die Zeit für DNS-Auflösung, Verbindungsherstellung, SSL/TLS-Handshake, Idle-Verbindungen, Schließen/Herunterfahren, sogar pro Sitzung begrenzen. Möglicherweise reicht es aus,
timeout-Schlüsselwortargumente zu einigen Methoden hinzuzufügen, und andere Zeitlimits können wahrscheinlich durch cleveren Einsatz voncall_later()undTask.cancel()implementiert werden. Es ist jedoch möglich, dass einige Operationen Standard-Zeitlimits benötigen und wir das Standardverhalten für eine bestimmte Operation global (d. h. pro Ereignisschleife) ändern möchten.
Referenzen
- PEP 492 beschreibt die Semantik von
async/await. - PEP 380 beschreibt die Semantik von
yield from. - Greg Ewings
yield fromTutorials: http://www.cosc.canterbury.ac.nz/greg.ewing/python/yield-from/yield_from.html - PEP 3148 beschreibt
concurrent.futures.Future. - PEP 3153, obwohl abgelehnt, enthält eine gute Beschreibung, die die Notwendigkeit erklärt, Transports und Protokolle zu trennen.
- PEP 418 diskutiert die Probleme der Zeitmessung.
- Tulip-Repo: http://code.google.com/p/tulip/
- PyPI: das Python Package Index unter http://pypi.python.org/
- Alyssa Coghlan hat einen schönen Blogbeitrag mit Hintergrundinformationen, Gedanken zu verschiedenen Ansätzen für asynchrone I/O, gevent und wie man Futures mit Konstrukten wie
while,forundwithverwendet: http://python-notes.boredomandlaziness.org/en/latest/pep_ideas/async_programming.html - TBD: Verweise auf die relevanten Teile von Twisted, Tornado, ZeroMQ, pyftpdlib, libevent, libev, pyev, libuv, wattle und so weiter.
Danksagungen
Neben PEP 3153 sind Einflüsse PEP 380 und Greg Ewings Tutorial für yield from, Twisted, Tornado, ZeroMQ, pyftpdlib und wattle (Steve Dowers Gegenentwurf). Meine frühere Arbeit an asynchroner Unterstützung in der NDB-Bibliothek für Google App Engine bot einen wichtigen Ausgangspunkt.
Ich bin dankbar für die zahlreichen Diskussionen auf python-ideas von September bis Dezember 2012 und viele weitere auf python-tulip seitdem; eine Skype-Sitzung mit Steve Dower und Dino Viehland; E-Mail-Austausch mit und ein Besuch von Ben Darnell; ein Gespräch mit Niels Provos (ursprünglicher Autor von libevent); und persönliche Treffen (sowie häufige E-Mail-Austausche) mit mehreren Twisted-Entwicklern, darunter Glyph, Brian Warner, David Reid und Duncan McGreggor.
Zu den Mitwirkenden an der Implementierung gehören Eli Bendersky, Gustavo Carneiro (Gambit Research), Saúl Ibarra Corretgé, Geert Jansen, A. Jesse Jiryu Davis, Nikolay Kim, Charles-François Natali, Richard Oudkerk, Antoine Pitrou, Giampaolo Rodolá, Andrew Svetlov und viele andere, die Fehler und/oder Korrekturen eingereicht haben.
Ich danke Antoine Pitrou für sein Feedback in seiner Rolle als offizieller PEP BDFL.
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-3156.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT