Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

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

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:

  • SelectorEventLoop ist eine konkrete Implementierung der vollständigen API, die auf dem selectors-Modul basiert (neu in Python 3.4). Der Konstruktor nimmt ein optionales Argument entgegen, ein selectors.Selector-Objekt. Standardmäßig wird eine Instanz von selectors.DefaultSelector erstellt und verwendet.
  • ProactorEventLoop ist 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, ein Proactor-Objekt. Standardmäßig wird eine Instanz von IocpProactor erstellt und verwendet. (Die Klasse IocpProactor wird von diesem PEP nicht spezifiziert; sie ist lediglich ein Implementierungsdetail der Klasse ProactorEventLoop.)

Ü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, bis stop() 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 denen run() ein anderes Verhalten hatte, teilweise, weil es bereits zu viele APIs gibt, die eine Methode namens run() 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 neues Task-Objekt, wenn der Parameter eine Coroutine ist.
  • stop(). Stoppt die Event Loop, sobald es angebracht ist. Es ist in Ordnung, die Loop danach mit run_forever() oder run_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“ (mit call_soon() geplante Callbacks) sie vor dem Stoppen verarbeitet.
  • is_running(). Gibt True zurück, wenn die Event Loop gerade läuft, und False, 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 von epoll() oder kqueue() 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(). Gibt True zurück, wenn die Event Loop geschlossen ist, andernfalls False. (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 ein Handle (siehe unten) zurück, das den Callback repräsentiert und dessen cancel()-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, dass callback(*args) ungefähr delay Sekunden in der Zukunft einmal aufgerufen wird, es sei denn, er wird abgebrochen. Gibt ein Handle zurück, das den Callback repräsentiert und dessen cancel()-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 wie call_later(), aber die Zeit wird als absolute Zeit ausgedrückt. Gibt ein ähnliches Handle zurück. Es gibt eine einfache Entsprechung: loop.call_later(delay, callback, *args) ist dasselbe wie loop.call_at(loop.time() + delay, callback, *args).
  • time(). Gibt die aktuelle Zeit gemäß der Uhr der Ereignisschleife zurück. Dies kann time.time() oder time.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). Wie call_soon(callback, *args), aber wenn von einem anderen Thread aufgerufen, während die Ereignisschleife auf E/A wartet, wird die Ereignisschleife entsperrt. Gibt ein Handle zurü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 Sie loop.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 Sie add_signal_handler(), das unten beschrieben wird.
  • run_in_executor(executor, callback, *args). Ordnet den Aufruf von callback(*args) in einem Executor an (siehe PEP 3148). Gibt eine asyncio.Future-Instanz zurück, deren Ergebnis im Erfolgsfall der Rückgabewert dieses Aufrufs ist. Dies ist äquivalent zu wrap_future(executor.submit(callback, *args)). Wenn executor None ist, wird der Standard-Executor verwendet, der von set_default_executor() festgelegt wurde. Wenn noch kein Standard-Executor festgelegt wurde, wird ein ThreadPoolExecutor mit 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 von run_in_executor() verwendet wird. Das Argument muss eine PEP 3148 Executor-Instanz oder None sein, 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 Funktion socket.getaddrinfo(), gibt aber einen Future zurück. Das Ergebnis des Futures im Erfolgsfall ist eine Liste im selben Format wie die von socket.getaddrinfo() zurückgegebene Liste, d.h. eine Liste von (address_family, socket_type, socket_protocol, canonical_name, address), wobei address ein 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 das family-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 den family-Wert eingeschränkt (ähnlich für proto und flags). Die Standardimplementierung ruft socket.getaddrinfo() über run_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, proto und flags nur unvollständig. Wenn jedoch type und proto ignoriert werden, sollten die übergebenen Argumentwerte unverändert in die Elemente socket_type und socket_protocol der Rückgabetupel kopiert werden. (family darf nicht ignoriert werden, da IPv4- und IPv6-Adressen unterschiedlich aufgelöst werden müssen. Die einzig zulässigen Werte für family sind socket.AF_UNSPEC (0), socket.AF_INET und socket.AF_INET6, und letzteres nur, wenn es von der Plattform definiert wird.)

  • getnameinfo(sockaddr, flags=0). Ähnlich wie socket.getnameinfo(), gibt aber einen Future zurück. Das Ergebnis des Futures im Erfolgsfall ist ein Tupel (host, port). Gleiche Implementierungsbemerkungen wie für getaddrinfo().

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 dann protocol_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 von protocol_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 Methode connection_made() des Protokolls noch nicht aufgerufen wurde; dies geschieht, wenn der Verbindungs-Handshake abgeschlossen ist.

    (*) Es besteht keine Anforderung, dass protocol_factory eine Klasse ist. Wenn Ihre Protokollklasse spezifische Argumente an ihren Konstruktor übergeben muss, können Sie lambda verwenden. Sie können auch eine triviale lambda übergeben, die eine zuvor erstellte Protokollinstanz zurückgibt.

    Die <options> werden alle über optionale Schlüsselwortargumente spezifiziert

    • ssl: Übergeben Sie True, um einen SSL/TLS-Transport zu erstellen (standardmäßig wird ein einfacher TCP-Transport erstellt). Oder übergeben Sie ein ssl.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 derzeit PROTOCOL_SSLv23 und setzt die Option OP_NO_SSLv2, ruft set_default_verify_paths() auf und setzt verify_mode auf CERT_REQUIRED. Zusätzlich, immer wenn der Kontext (Standard oder ein anderer) einen verify_mode von CERT_REQUIRED oder CERT_OPTIONAL angibt, und wenn ein Hostname angegeben wird, wird unmittelbar nach einem erfolgreichen Handshake ssl.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, dessen verify_mode auf CERT_NONE gesetzt 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 an getaddrinfo() weitergegeben werden. Diese alle standardmäßig auf 0, was bedeutet "nicht spezifiziert". (Der Socket-Typ ist immer SOCK_STREAM.) Wenn einer dieser Werte nicht angegeben ist, wählt die Methode getaddrinfo() geeignete Werte aus. Hinweis: proto hat nichts mit dem High-Level-Protokollkonzept oder dem Argument protocol_factory zu tun.
    • sock: Ein optionaler Socket, der anstelle der Argumente host, port, family, proto und flags verwendet wird. Wenn dies angegeben ist, müssen host und port explizit auf None gesetzt 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 mit getaddrinfo() aufgelöst.
    • server_hostname: Dies ist nur relevant, wenn SSL/TLS verwendet wird; es sollte nicht verwendet werden, wenn ssl nicht gesetzt ist. Wenn ssl gesetzt ist, legt dies den zu verifizierenden Hostnamen fest oder überschreibt ihn. Standardmäßig wird der Wert des Arguments host verwendet. Wenn host leer ist, gibt es keinen Standardwert und Sie müssen einen Wert für server_hostname angeben. Um die Hostnamenverifizierung zu deaktivieren (was ein ernstes Sicherheitsrisiko darstellt), müssen Sie hier einen leeren String übergeben und ein ssl.SSLContext-Objekt übergeben, dessen verify_mode auf ssl.CERT_NONE gesetzt ist, als Argument ssl.
  • 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 ein Server-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_factory ohne 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 von protocol.connection_made(transport) zusammengebunden.

    (**) Siehe vorherige Fußnote für create_connection(). Da jedoch protocol_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 ein ssl.SSLContext-Objekt (oder ein Objekt mit derselben Schnittstelle), um das Standard-SSL-Kontextobjekt zu überschreiben. (Im Gegensatz zu create_connection() ist das Übergeben von True hier nicht sinnvoll – das SSLContext-Objekt wird benötigt, um das Zertifikat und den Schlüssel anzugeben.)
    • backlog: Wert für die Warteschlange, der an den listen()-Aufruf übergeben wird. Der Standardwert ist implementierungsabhängig; in der Standardimplementierung beträgt der Standardwert 100.
    • reuse_address: Ob die Option SO_REUSEADDR für den Socket gesetzt werden soll. Der Standardwert ist True unter UNIX, False unter Windows.
    • family, flags: Adressfamilie und Flags, die an getaddrinfo() weitergegeben werden. Die Familie standardmäßig auf AF_UNSPEC; die Flags standardmäßig auf AI_PASSIVE. (Der Socket-Typ ist immer SOCK_STREAM; das Socket-Protokoll immer auf 0 gesetzt, damit getaddrinfo() wählen kann.)
    • sock: Ein optionaler Socket, der anstelle der Argumente host, port, family und flags verwendet wird. Wenn dies angegeben ist, müssen host und port explizit auf None gesetzt 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 ein DatagramTransport. Das zurückgegebene Protokoll ist ein DatagramProtocol. 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 an getaddrinfo() übergeben, um aufgelöst zu werden, und das Ergebnis wird an die bind()-Methode des erstellten Sockets übergeben. Wenn getaddrinfo() mehr als eine Adresse zurückgibt, werden diese nacheinander versucht. Wenn dies weggelassen wird, wird kein bind()-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 an getaddrinfo() übergeben, um aufgelöst zu werden, und das Ergebnis wird zusammen mit dem erstellten Socket an sock_connect() übergeben. Wenn getaddrinfo() mehr als eine Adresse zurückgibt, werden diese nacheinander versucht. Wenn dies weggelassen wird, wird kein sock_connect()-Aufruf erfolgen.

    Die <options> werden alle über optionale Schlüsselwortargumente spezifiziert

    • family, proto, flags: Adressfamilie, Protokoll und Flags, die an getaddrinfo() weitergegeben werden. Diese alle standardmäßig auf 0, was bedeutet "nicht spezifiziert". (Der Socket-Typ ist immer SOCK_DGRAM.) Wenn einer dieser Werte nicht angegeben ist, wählt die Methode getaddrinfo() geeignete Werte aus.

    Beachten Sie, dass, wenn sowohl local_addr als auch remote_addr vorhanden 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 zu n Bytes vom Socket sock. Gibt einen Future zurück, dessen Ergebnis im Erfolgsfall ein Bytes-Objekt ist.
  • sock_sendall(sock, data). Sendet Bytes data an den Socket sock. Gibt einen Future zurück, dessen Ergebnis im Erfolgsfall None ist. Hinweis: Der Name verwendet sendall anstelle von send, um widerzuspiegeln, dass die Semantik und Signatur dieser Methode denen der Standardbibliotheks-Socketmethode sendall() und nicht send() entsprechen.
  • sock_connect(sock, address). Verbindet sich mit der angegebenen Adresse. Gibt einen Future zurück, dessen Ergebnis im Erfolgsfall None ist.
  • 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, wobei conn ein verbundener nicht-blockierender Socket und peer die 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, dass callback(*args) aufgerufen wird, wann immer der Dateideskriptor fd zum Lesen bereit ist. Der Aufruf von add_reader() erneut für denselben Dateideskriptor impliziert einen Aufruf von remove_reader() für denselben Dateideskriptor.
  • add_writer(fd, callback, *args). Wie add_reader(), registriert den Callback aber zum Schreiben statt zum Lesen.
  • remove_reader(fd). Bricht den aktuellen Lese-Callback für den Dateideskriptor fd ab, falls einer gesetzt ist. Wenn derzeit kein Callback für den Dateideskriptor gesetzt ist, ist dies ein No-Op und gibt False zurück. Andernfalls entfernt es die Callback-Anordnung und gibt True zurück.
  • remove_writer(fd). Dies verhält sich zu add_writer(), wie sich remove_reader() zu add_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 ein ReadTransport.
  • 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 ein WriteTransport; er hat keine Lese-bezogenen Methoden. Das zurückgegebene Protokoll ist ein BaseProtocol.
  • subprocess_shell(protocol_factory, cmd, <options>): Erzeugt einen Subprozess aus cmd, einer Zeichenkette, die die "Shell"-Syntax der Plattform verwendet. Dies ähnelt der Klasse subprocess.Popen() der Standardbibliothek, die mit shell=True aufgerufen 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 den sys.argv-Wert des Programms, vorausgesetzt, es handelt sich um ein Python-Skript.) Dies ähnelt der Klasse subprocess.Popen() der Standardbibliothek, die mit shell=False und der Liste von Zeichenketten als erstes Argument aufgerufen wird; wo jedoch Popen() ein einzelnes Argument erhält, das eine Liste von Zeichenketten ist, erhält subprocess_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 über connect_write_pipe() verbunden wird, oder die Konstante subprocess.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 über connect_read_pipe() verbunden wird, oder die Konstante subprocess.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 über connect_read_pipe() verbunden wird, oder eine der Konstanten subprocess.PIPE (Standard) oder subprocess.STDOUT. Standardmäßig wird eine neue Pipe erstellt und verbunden. Wenn subprocess.STDOUT angegeben 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 an subprocess.Popen() übergeben. In der Standardimplementierung ist der Standardwert Null, und unter Windows muss er Null sein; diese Standardwerte weichen von subprocess.Popen() ab.
  • executable, preexec_fn, close_fds, cwd, env, startupinfo, creationflags, restore_signals, start_new_session, pass_fds: Diese optionalen Argumente werden ohne Interpretation an subprocess.Popen() übergeben.

Signal-Callbacks

Diese Methoden werden nur unter UNIX unterstützt.

  • add_signal_handler(sig, callback, *args). Immer wenn das Signal sig empfangen wird, wird arrangiert, dass callback(*args) aufgerufen wird. Die Angabe eines anderen Callbacks für dasselbe Signal ersetzt den vorherigen Handler (nur ein Handler kann pro Signal aktiv sein). sig muss eine gültige Signalnummer sein, die im Modul signal definiert 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 Signal sig, falls einer gesetzt ist. Löst dieselben Ausnahmen aus wie add_signal_handler() (außer dass sie False zurückgeben kann, anstatt RuntimeError für nicht abfangbare Signale auszulösen). Gibt True zurü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 von set_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_duration steuert 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() gibt True zurück, wenn der *Debug*-Modus aktiviert ist, andernfalls False.
  • set_debug(enabled) aktiviert den *Debug*-Modus, wenn das Argument True ist.

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 Sie False zurück. Andernfalls wird versucht, das Future abzubrechen und True zurückzugeben. Wenn der Abbruchversuch erfolgreich ist, wird der Zustand des Futures schließlich auf abgebrochen geändert (so dass cancelled() True zurü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(). Gibt True zurück, wenn das Future erfolgreich abgebrochen wurde.
  • done(). Gibt True zurück, wenn das Future abgeschlossen ist. Beachten Sie, dass ein abgebrochenes Future ebenfalls als abgeschlossen gilt (hier und überall).
  • result(). Gibt das mit set_result() gesetzte Ergebnis zurück oder löst die mit set_exception() gesetzte Ausnahme aus. Löst CancelledError aus, 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 mit set_exception() gesetzt wurde, oder None, wenn ein Ergebnis mit set_result() gesetzt wurde. Löst CancelledError aus, 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 von call_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 über call_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 mit call_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 an add_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 von set_result() auf einem bereits abgeschlossenen Future oder Aufruf von result() auf einem noch nicht abgeschlossenen Future).
  • InvalidTimeoutError. Wird von result() und exception() ausgelöst, wenn ein nicht-null timeout-Argument angegeben wird.
  • CancelledError. Ein Alias für concurrent.futures.CancelledError. Wird ausgelöst, wenn result() oder exception() auf einem abgebrochenen Future aufgerufen wird.
  • TimeoutError. Ein Alias für concurrent.futures.TimeoutError. Kann von run_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 mit yield from verwenden 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, dass Task eine Unterklasse von Future ist) eingepackt.
  • asyncio.wrap_future(future). Dies nimmt ein PEP 3148 Future (d.h. eine Instanz von concurrent.futures.Future) und gibt ein mit der Event-Loop kompatibles Future zurück (d.h. eine asyncio.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. Gibt None zurü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 zu t.write(b'abcdef'), sowie zu
    t.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 von write() 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 von write_eof() eine Ausnahme aus. (Hinweis: Früher hieß diese Methode half_close(), aber wenn Sie nicht bereits wissen, wozu sie dient, gibt dieser Name nicht an, *welches* Ende geschlossen wird.)
  • can_write_eof(). Gibt True zurück, wenn das Protokoll write_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, wenn write_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 mit write_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() und resume_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, dass resume_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 von resume_reading() erfolgt. Zwischen pause_reading() und resume_reading() wird die Methode data_received() des Protokolls nicht aufgerufen.
  • resume_reading(). Setzt die Datenlieferung an das Protokoll über data_received() fort. Beachten Sie, dass "pausiert" ein binärer Zustand ist – pause_reading() sollte nur aufgerufen werden, wenn der Transport nicht pausiert ist, während resume_reading() nur aufgerufen werden sollte, wenn der Transport pausiert ist.
  • close(). Trennt die Verbindung zur Entität am anderen Ende. Alle von write() gepufferten Daten werden (schließlich) übertragen, bevor die Verbindung tatsächlich geschlossen wird. Die Methode data_received() des Protokolls wird nicht erneut aufgerufen. Sobald alle gepufferten Daten geleert sind, wird die Methode connection_lost() des Protokolls mit None als 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 Methode connection_lost() des Protokolls mit None als 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, muss remote_addr im Aufruf create_datagram_endpoint(), der diesen Transport erstellt hat, angegeben worden sein. Wenn es vorhanden ist und remote_addr angegeben wurde, müssen sie übereinstimmen. Das (data, addr)-Paar kann sofort gesendet oder gepuffert werden. Der Rückgabewert ist None.
  • 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; andernfalls None.
  • 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, wird None zurü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ür terminate().
  • close(). Dies ist ein Alias für terminate().

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 seine write()- 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 wie
    p.data_received(b'abc')
    p.data_received(b'def')
    
  • eof_received(). Dies wird aufgerufen, wenn das andere Ende write_eof() (oder etwas Äquivalentes) aufgerufen hat. Wenn dies einen falschen Wert (einschließlich None) 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 None zurü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 Methode set_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 der write()-Methode des Transports aufgerufen werden kann (im Gegensatz zum indirekten Aufruf über call_soon()), sodass sich das Protokoll seines pausierten Zustands unmittelbar nach Rückgabe von write() 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 Argument None; 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

  1. connection_made() – genau einmal
  2. data_received() – null oder mehrfach
  3. eof_received() – höchstens einmal
  4. connection_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 Datagramm data (ein Bytes-Objekt) von der Remote-Adresse addr (ein IPv4 2-Tupel oder ein IPv6 4-Tupel) empfangen wurde.
  • error_received(exc). Zeigt an, dass eine Sende- oder Empfangsoperation eine OSError-Ausnahme ausgelöst hat. Da Datagramm-Fehler vorübergehend sein können, liegt es am Protokoll, die Methode close() des Transports aufzurufen, wenn die Endpunkt geschlossen werden soll.

Hier ist eine Tabelle, die die Reihenfolge und Anzahl der Aufrufe angibt

  1. connection_made() – genau einmal
  2. datagram_received(), error_received() – null oder mehrfach
  3. connection_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. fd ist der Dateideskriptor (1 für stdout, 2 für stderr). data ist ein bytes-Objekt.
  • pipe_connection_lost(fd, exc). Wird aufgerufen, wenn der Subprozess stdin, stdout oder stderr schließt. fd ist der Dateideskriptor. exc ist eine Ausnahme oder None.
  • process_exited(). Wird aufgerufen, wenn der Subprozess beendet wurde. Um den Exit-Status abzurufen, verwenden Sie die Methode get_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.coroutine dekorierte 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 eine CancelledError-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 Ausdruck coroutine muss ein Aufruf einer anderen Coroutine sein.
  • return expression – liefert ein Ergebnis an die Coroutine, die mit yield from auf diese wartet.
  • raise exception – löst eine Ausnahme in der Coroutine aus, die mit yield from auf 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 von fs bereitgestellt 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), wobei done die Menge der ursprünglichen Futures (oder verpackten Coroutinen) ist, die abgeschlossen (oder abgebrochen) sind, und pending der Rest ist, d. h. diejenigen, die noch nicht abgeschlossen (noch nicht abgebrochen) sind. Beachten Sie, dass mit den Standardwerten für timeout und return_when done immer eine leere Liste sein wird. Optionale Argumente timeout und return_when haben dieselbe Bedeutung und Standardwerte wie für concurrent.futures.wait(): timeout gibt, wenn nicht None, ein Timeout für die Gesamtoperation an; return_when gibt an, wann gestoppt werden soll. Die Konstanten FIRST_COMPLETED, FIRST_EXCEPTION, ALL_COMPLETED sind 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 Menge fs abgeschlossen ist, und gibt ihr Ergebnis zurück (oder löst ihre Ausnahme aus). Das optionale Argument timeout hat dieselbe Bedeutung und denselben Standardwert wie für concurrent.futures.wait(): Wenn das Timeout eintritt, löst die vom Iterator zurückgegebene nächste Future TimeoutError aus, wenn darauf gewartet wird. Beispiel für die Verwendung
    for 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 in shield().
  • 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 mit asyncio.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 loop None ist, gibt die Methode den aktuellen Task für die Standard-Schleife zurück. Jede Coroutine wird innerhalb eines Task-Kontexts ausgeführt, entweder eines Task, der mit ensure_future() oder loop.create_task() erstellt wurde, oder indem er von einer anderen Coroutine über yield from oder await aufgerufen wird. Diese Methode gibt None zurück, wenn sie außerhalb einer Coroutine aufgerufen wird, z. B. in einem Callback, der über loop.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 loop None ist.

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ür EventLoop.create_connection(), der keine Angabe einer Protocol-Factory oder -Klasse erfordert. Dies ist eine Coroutine, die ein (reader, writer)-Paar zurückgibt, wobei reader eine Instanz von StreamReader und writer eine Instanz von StreamWriter ist (beide unten beschrieben).
  • asyncio.start_server(client_connected_cb, host, port): Ein Wrapper für EventLoop.create_server(), der eine einfache Callback-Funktion anstelle einer Protocol-Factory oder -Klasse entgegennimmt. Dies ist eine Coroutine, die ein Server-Objekt zurückgibt, genau wie create_server(). Jedes Mal, wenn eine Client-Verbindung angenommen wird, wird client_connected_cb(reader, writer) aufgerufen, wobei reader eine Instanz von StreamReader und writer eine Instanz von StreamWriter ist (beide unten beschrieben). Wenn das von client_connected_cb() zurückgegebene Ergebnis eine Coroutine ist, wird es automatisch in einen Task verpackt.
  • 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 einer StreamReaderProtocol-Instanz gesteuert. Beachten Sie, dass es nur einen Reader geben sollte. Die Schnittstelle für den Reader ist
    • readline(): 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 zu n Bytes liest. Wenn n weggelassen wird oder negativ ist, liest sie bis zum Ende des Streams.
    • readexactly(n): Eine Coroutine, die genau n Bytes liest, oder bis zum Ende des Streams, je nachdem, was zuerst eintritt.
    • exception(): Gibt die Ausnahme zurück, die mit set_exception() auf dem Stream gesetzt wurde, oder None, wenn keine Ausnahme gesetzt ist.

    Die Schnittstelle für den Treiber ist

    • feed_data(data): Hängt data (ein bytes-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 der StreamReader-Klasse). Die folgende Methode ist zusätzlich zur Transportschnittstelle
    • drain(): Dies sollte mit yield from aufgerufen werden, nachdem signifikante Daten geschrieben wurden, zum Zweck der Flusskontrolle. Die beabsichtigte Verwendung ist wie folgt
      writer.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 den StreamWriter zugrunde 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 gelegentlich yield 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 Klassen StreamReader und StreamWriter dient. Sie fungiert als Treiber für eine bestimmte StreamReader-Instanz und ruft deren Methoden feed_data(), feed_eof() und set_exception() als Reaktion auf verschiedene Protokoll-Callbacks auf. Sie steuert auch das Verhalten der drain()-Methode der StreamWriter-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 Methoden acquire() (eine Coroutine), locked() und release().
  • Event: eine Ereignisvariable mit den Methoden wait() (eine Coroutine), set(), clear() und is_set().
  • Condition: eine Bedingungsvariable mit den Methoden acquire(), wait(), wait_for(predicate) (alle drei Coroutinen), locked(), release(), notify() und notify_all().
  • Semaphore: ein Semaphor mit den Methoden acquire() (eine Coroutine), locked() und release(). Das Konstruktorargument ist der Anfangswert (Standard 1).
  • BoundedSemaphore: ein begrenzter Semaphor; dies ähnelt Semaphore, aber der Anfangswert ist auch der Maximalwert.

Queues

Die folgenden Klassen und Ausnahmen werden von asyncio.queues bereitgestellt.

  • Queue: eine Standardwarteschlange mit den Methoden get(), put() (beide Coroutinen), get_nowait(), put_nowait(), empty(), full(), qsize() und maxsize().
  • PriorityQueue: eine Unterklasse von Queue, die Einträge in der Reihenfolge der Priorität (niedrigste zuerst) abruft.
  • LifoQueue: eine Unterklasse von Queue, die die zuletzt hinzugefügten Einträge zuerst abruft.
  • JoinableQueue: eine Unterklasse von Queue mit den Methoden task_done() und join() (letztere ist eine Coroutine).
  • Empty, Full: Ausnahmen, die ausgelöst werden, wenn get_nowait() oder put_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() und create_datagram_endpoint() ein proto-Argument, aber create_server() nicht? Und warum sind die Argumente family, flag, proto für getaddrinfo() 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() und sock_recvfrom(), und vielleicht andere wie pipe_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 von call_later() und Task.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

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.


Quelle: https://github.com/python/peps/blob/main/peps/pep-3156.rst

Zuletzt geändert: 2025-02-01 08:59:27 GMT