Als ich die Möglichkeiten von MicroPython für seine Zwecke studierte, stieß ich auf eine der Implementierungen der Asyncio- Bibliothek und nach einer kurzen Korrespondenz mit Piter Hinch , dem Autor der Bibliothek, erkannte ich, dass ich die Prinzipien, Grundkonzepte und typischen Fehler bei der Verwendung asynchroner Programmiermethoden besser verstehen musste. Außerdem ist der Abschnitt für Anfänger nur für mich.Dieses Handbuch richtet sich an Benutzer mit unterschiedlichen Erfahrungen mit
Asyncio , einschließlich eines speziellen Abschnitts für Anfänger.
Inhalt0. Einleitung0.1 .___
uasyncio auf einem leeren Gerät (Hardware) installieren
1. Planung für die gemeinsame Programmausführung1.1 .___ Module
2. Uasyncio- Bibliothek2.1 .___ Programmstruktur: Ereignisverarbeitungszyklus
2.2 .___ Koroutinen
2.2.1 .______ Einreihen von
Coroutinen zur Teilnahme an der Planung2.2.2 .______
Funktionsrückruf starten
( Callback )2.2.3 .______
Anmerkungen: Koroutinen als verwandte Methoden. Die Rückgabewerte.2.3 .___ Verspätungen
3. Synchronisation und ihre Klassen3.1 .___ Sperre
Sperre3.1.1 .______
Sperren und Zeitüberschreitungen3.2 .___
Ereignis3.2.1 .______ Ereigniswert
3.3 .___ Barriere
Barriere3.4 .___
Semaphor3.4.1 .______
Limited Semaphore3.5 .___ Warteschlange
Warteschlange3.6 .___ Andere Synchronisationsklassen
4. Klassenentwicklung für Asyncio4.1 .___ Klassen mit wait
4.1.1 .______
Verwendung in Kontextmanagern4.1.2 .______
In der Coroutine warten
4.2 .___ Asynchrone Iteratoren
4.3 .___ Asynchrone Kontextmanager
5. Ausnahmen zu Zeitüberschreitungen und aufgrund von Aufgabenstornierungen5.1 .___ Ausnahmen
5.2 .___ Ausnahmen aufgrund von Timeouts und aufgrund von Abbrüchen von Aufgaben
5.2.1 .______
Aufgaben abbrechen
5.2.2 .______
Koroutinen mit Timeouts6. Interaktion mit Hardwaregeräten6.1 .___ Synchronisierungsprobleme
6.2 .___ Abfragen von Geräten mit Coroutinen
6.3 .___ Verwenden der Streaming-Engine
6.3.1 .______
UART- Treiberbeispiel
6.4 .___ Treiberentwicklung für ein Streaming-Gerät
6.5 .___ Vollständiges Beispiel:
aremote.py Treiber für IR-Fernbedienungsempfänger.
6.6 .___ Treiber für Temperatur- und Feuchtigkeitssensor HTU21D.
7. Tipps und Tricks7.1 .___ Programm friert ein
7.2 .___
uasyncio speichert den Zustand
7.3 .___ Speicherbereinigung
7.4 .___ Testen
7.5 .___ Häufiger Fehler. Es kann schwer zu finden sein.
7.6 .___ Programmieren mit Sockets (
Sockets )
7.6.1 .______
WLAN-Probleme7.7 .___ Argumente des Event-Loop-Konstruktors
8. Hinweise für Anfänger8.1 .___ Problem 1: Ereignisschleifen
8.2 .___ Problem 2: Sperrmethoden
8.3 .___ Der
Uasyncio- Ansatz
8.4 .___ Planung in
uasyncio8.5 .___ Warum kollaboratives, nicht threadbasiertes Scheduling (
_thread )?
8.6 .___ Interaktion
8.7 .___
Polling0. EinleitungDer größte Teil dieses Dokuments setzt eine gewisse Vertrautheit mit der asynchronen Programmierung voraus. Für Anfänger finden Sie eine Einführung in Abschnitt 7.
Die
uasyncio- Bibliothek für
MicroPython enthält eine Teilmenge der
asyncio- Python- Bibliothek und ist für die Verwendung auf Mikrocontrollern vorgesehen. Daher nimmt es nur wenig RAM ein und ist so konfiguriert, dass Kontexte ohne RAM-Zuweisung schnell umgeschaltet werden.
In diesem Dokument wird die Verwendung von
uasyncio beschrieben, wobei der Schwerpunkt auf der Erstellung von Treibern für Hardwaregeräte liegt.
Ziel ist es, die Treiber so zu gestalten, dass die Anwendung weiterhin funktioniert, während der Treiber auf eine Antwort vom Gerät wartet. Gleichzeitig reagiert die Anwendung empfindlich auf andere Ereignisse und Benutzerinteraktionen.
Ein weiteres wichtiges Einsatzgebiet von
asyncio ist die Netzwerkprogrammierung: Im Internet finden Sie genügend Informationen zu diesem Thema.
Beachten Sie, dass
MicroPython auf
Python 3.4 mit den minimalen
Python 3.5- Add-Ons basiert. Funktionen von
Asyncio- Versionen, die älter als 3.4 sind, werden nicht unterstützt, es sei denn, dies wird im
Folgenden beschrieben . In diesem Dokument werden die Funktionen definiert, die in dieser Untergruppe unterstützt werden.
In diesem Handbuch wird ein Programmierstil vorgestellt, der mit
CPython V3.5 und höher kompatibel ist.
0.1 Installieren Sie uasyncio auf einem leeren Gerät (Hardware)Es wird empfohlen, die Firmware
MicroPython V1.11 oder höher zu verwenden. Auf vielen Plattformen ist keine Installation erforderlich, da
uasyncio® bereits in der Assembly kompiliert ist. Um dies zu überprüfen, geben Sie einfach REPL ein
import uasyncio
Die folgenden Anweisungen decken Fälle ab, in denen die Module nicht vorinstalliert sind. Die
Warteschlangen und
Synchronisationsmodule sind optional, werden jedoch benötigt, um die hier angegebenen Beispiele auszuführen.
Gerät mit InternetverbindungAuf einem mit dem Internet verbundenen Gerät, auf dem Firmware V1.11 oder höher ausgeführt wird, können Sie mit der integrierten
Upip- Version installieren. Stellen Sie sicher, dass das Gerät mit Ihrem Netzwerk verbunden ist:
import upip upip.install ( 'micropython-uasyncio' ) upip.install ( 'micropython-uasyncio.synchro' ) upip.install ( 'micropython-uasyncio.queues' )
Die Fehlermeldungen von
upip sind nicht sehr nützlich. Wenn Sie einen unverständlichen Fehler erhalten, überprüfen Sie die Internetverbindung erneut.
Hardware ohne Internetverbindung ( Micropip )Wenn Ihr Gerät nicht über eine Internetverbindung verfügt (z. B.
Pyboard V1.x ),
starten Sie am einfachsten die Installation von
micropip.py auf dem Computer in das Verzeichnis Ihrer Wahl und kopieren Sie die resultierende Verzeichnisstruktur auf das Zielgerät. Das Dienstprogramm
micropip.py läuft unter
Python 3.2 oder höher und unter Linux, Windows und OSX. Weitere Informationen finden Sie
hier .
Typischer Anruf:
$ micropip.py install -p ~/rats micropython-uasyncio $ micropip.py install -p ~/rats micropython-uasyncio.synchro $ micropip.py install -p ~/rats micropython-uasyncio.queues
Ein Gerät ohne Internetverbindung (Kopierquelle)Wenn Sie
micropip.py nicht verwenden, müssen die Dateien von der Quelle kopiert werden. In den folgenden Anweisungen wird beschrieben, wie die Mindestanzahl von Dateien auf das Zielgerät
kopiert wird. Außerdem wird der Fall beschrieben, in dem
uasyncio in Form eines Bytecodes in eine kompilierte Assembly komprimiert werden muss, um den belegten Speicherplatz zu verringern. Für die neueste Version, die mit der offiziellen Firmware kompatibel ist, müssen die Dateien von der offiziellen
Micropython-Lib- Website kopiert werden.
Klonen Sie die Bibliothek mit dem Befehl auf den Computer
$ git clone https://github.com/micropython/micropython-lib.git
Erstellen Sie auf dem
Zielgerät das Verzeichnis
uasyncio (optional im Verzeichnis lib) und kopieren Sie die folgenden Dateien hinein:
• uasyncio / uasyncio / __ init__.py
• uasyncio.core / uasyncio / core.py
• uasyncio.synchro / uasyncio / synchro.py
• uasyncio.queues / uasyncio / queues.py
Diese
uasyncio- Module können zu Bytecode komprimiert werden, indem das Verzeichnis
uasyncio und sein Inhalt in den Port des Verzeichnisses
modules gestellt und der Inhalt neu kompiliert wird.
1. Gemeinsame PlanungDie Technik der gemeinsamen Ausführung mehrerer Tasks ist in eingebetteten Systemen weit verbreitet und bietet weniger Overhead als die
Thread- Planung (
_thread ), wodurch viele Fallstricke vermieden werden, die mit wirklich asynchronen Threads verbunden sind.
1.1 ModuleIm Folgenden finden Sie eine Liste der Module, die auf dem Zielgerät ausgeführt werden können.
Bibliotheken1.
asyn.py Stellt
Sperre, Ereignis, Barriere, Semaphor, BoundedSemaphor, Bedingung und Sammelsynchronisationsprimitive bereit . Bietet Unterstützung für das Abbrechen von Aufgaben über die Klassen
NamedTask und
Cancellable .
2.
aswitch.py Repräsentiert Klassen zum
Koppeln von Schaltern und Tasten sowie ein Programmobjekt mit der Möglichkeit einer wiederholten Verzögerung. Tasten sind eine Verallgemeinerung von Schaltern, die eher einen logischen als einen physischen Zustand bereitstellen, sowie von Ereignissen, die durch zweimaliges und langes Drücken ausgelöst werden.
Demo-ProgrammeDie ersten beiden sind am nützlichsten, da sie beim Zugriff auf die
Pyboard-Hardware sichtbare Ergebnisse
liefern .
- aledflash.py Blinkt vier Pyboard- Anzeigen 10 Sekunden lang asynchron. Die einfachste Demonstration von Uasyncio . Importieren Sie es zum Ausführen.
- apoll.py Gerätetreiber für den Pyboard- Beschleunigungssensor. Veranschaulicht die Verwendung von Coroutinen zum Abfragen eines Geräts. Funktioniert für 20 s. Importieren Sie es zum Ausführen. Benötigt Pyboard V1.x.
- astests.py Test- / Demoprogramme für das Aswitch- Modul.
- asyn_demos.py Einfache Demos zum Abbrechen von Aufgaben.
- roundrobin.py Demonstration der Kreisplanung . Auch der Maßstab für die Leistungsplanung.
- awaitable.py Demonstration einer Klasse mit einer Wartezeit. Eine Möglichkeit, einen Gerätetreiber zu implementieren, der eine Schnittstelle abfragt.
- chain.py Kopiert aus der Python- Dokumentation. Demonstration der Coroutine-Kette.
- aqtest.py Demonstration der Queue- Klasse der uasyncio- Bibliothek.
- aremote.py Beispielgerätetreiber für das NEC-IR-Protokoll.
- auart.py Demonstration von Streaming Input-Output über Pyboard UART .
- auart_hd.py Verwenden von Pyboard UART zur Kommunikation mit einem Gerät unter Verwendung des Halbduplex-Protokolls. Geeignet für Geräte, die beispielsweise den Befehlssatz AT-Modem verwenden.
- iorw.py Demonstration eines Lese- / Schreibgeräts mit Streaming-E / A.
Testprogramme- asyntest.py Prüft auf Synchronisationsklassen in asyn.py.
- cantest.py Abbruchtests .
Dienstprogramm1.
check_async_code.py Das Dienstprogramm wurde in
Python3 geschrieben , um bestimmte Codierungsfehler zu erkennen, die möglicherweise schwer zu finden sind. Siehe Abschnitt 7.5.
KontrolleDas
Benchmark- Verzeichnis enthält Skripte zur Überprüfung und Charakterisierung des
uasyncio-Schedulers .
2. Uasyncio- BibliothekDas
asyncio- Konzept basiert auf der Organisation der Planung für die gemeinsame Ausführung mehrerer Aufgaben, die in diesem Dokument als
Coroutinen bezeichnet werden .
2.1 Programmstruktur: EreignisschleifeBetrachten Sie das folgende Beispiel:
import uasyncio as asyncio async def bar (): count = 0, while True : count + = 1 print ( count ) await asyncio.sleep ( 1 )
Die Programmausführung wird fortgesetzt, bis
loop.run_forever aufgerufen wird . Zu diesem Zeitpunkt wird die Ausführung vom Scheduler gesteuert. Die Zeile nach
loop.run_forever wird niemals ausgeführt. Der Scheduler führt den
Barcode aus , da er im
Scheduler loop.create_task in die
Warteschlange gestellt wurde . In diesem einfachen Beispiel gibt es nur einen Koroutinenstab. Wenn es andere gäbe, würde der Scheduler sie in Zeiträumen ausführen, in denen die
Leiste angehalten wurde.
Die meisten eingebetteten Anwendungen verfügen über eine kontinuierliche Ereignisschleife. Eine Ereignisschleife kann auch so gestartet werden, dass sie mit der Ereignisschleifenmethode
run_until_complete abgeschlossen werden kann. Es wird hauptsächlich zum Testen verwendet. Beispiele finden Sie im
astests.py- Modul.
Eine Ereignisschleifeninstanz ist ein einzelnes Objekt, das durch den ersten Aufruf von
asyncio.get_event_loop () mit zwei optionalen Ganzzahlargumenten erstellt wird, die die Anzahl der Coroutinen in den beiden Warteschlangen angeben - Start und Warten. In der Regel haben beide Argumente den gleichen Wert, der mindestens der Anzahl der gleichzeitig ausgeführten Coroutinen in der Anwendung entspricht. In der Regel ist der Standardwert 16 ausreichend.Wenn nicht-Standardwerte verwendet werden, lesen Sie Argumente des Konstruktors für Ereignisschleifen (Abschnitt 7.7.).
Wenn die Coroutine die Ereignisschleifenmethode aufrufen
muss ( normalerweise
create_task ), wird sie durch Aufrufen von
asyncio.get_event_loop () (ohne Argumente) zurückgegeben.
2.2 KoroutinenEine Coroutine wird wie folgt erstellt:
async def foo ( delay_secs ): await asyncio.sleep ( delay_secs ) print ( 'Hello' )
Mit einer Coroutine können andere Coroutinen mit der Anweisung
await gestartet werden. Eine Coroutine muss mindestens eine
wait- Anweisung enthalten. Dies bewirkt, dass die Coroutine vor dem Abschluss ausgeführt wird, bevor die Ausführung mit der nächsten Anweisung fortgesetzt wird. Betrachten Sie ein Beispiel:
await asyncio.sleep ( delay_secs ) await asyncio.sleep ( 0 )
In der ersten Zeile wird der Code für eine Verzögerungszeit angehalten, während andere Coroutinen diese Zeit für ihre Ausführung verwenden. Eine Verzögerung von 0 bewirkt, dass alle anstehenden Coroutinen in einer zyklischen Reihenfolge ausgeführt werden, bis die nächste Zeile ausgeführt wird. Siehe das Beispiel von
roundrobin.py .
2.2.1. Warteschlange für die Planung einer Coroutine- EventLoop.create_task Argument: Auszuführende Coroutine. Der Scheduler stellt die Coroutine in eine Warteschlange, damit sie so schnell wie möglich startet. Der Aufruf von create_task wird sofort zurückgegeben. Die Koroutine im Argument wird in der Syntax des Funktionsaufrufs mit den erforderlichen Argumenten angegeben.
- EventLoop.run_until_complete Argument: Auszuführende Coroutine. Der Scheduler stellt die Coroutine in eine Warteschlange, damit sie so schnell wie möglich startet. Die Koroutine im Argument wird in der Syntax des Funktionsaufrufs mit den erforderlichen Argumenten angegeben. Der Aufruf un_until_complete gibt zurück, wenn die Coroutine abgeschlossen ist: Diese Methode bietet eine Möglichkeit, den Scheduler zu beenden.
- wait Argument: Eine auszuführende Coroutine, die mithilfe der Funktionsaufrufsyntax angegeben wird. Startet so schnell wie möglich eine Coroutine. Die anstehende Coroutine wird blockiert, bis eine der erwarteten Coroutinen abgeschlossen ist.
Das obige ist mit
CPython kompatibel. Weitere
Uasyncio- Methoden
werden im Anhang (Abschnitt 2.2.3.) Erläutert.
2.2.2 Rückruffunktion startenRückrufe sollten
Python- Funktionen sein, die so konzipiert sind, dass sie in kurzer Zeit ausgeführt werden. Dies liegt an der Tatsache, dass Coroutinen nicht für die gesamte Dauer der Ausführung einer solchen Funktion arbeiten können.
Die folgenden
EventLoop- Klassenmethoden verwenden Rückrufe:
- call_soon - ruf so schnell wie möglich an. Argumente: Rückruf Rückruf, um auszuführen, * Argumente, auf die Positionsargumente möglicherweise ein Komma folgt.
- call_later - ruft nach einer Verzögerung in Sekunden an. Argumente: Verzögerung, Rückruf, * Argumente
- call_later_ms - ruft nach einer Verzögerung in ms auf. Argumente: Verzögerung, Rückruf, * Argumente .
loop = asyncio.get_event_loop () loop.call_soon ( foo , 5 )
2.2.3 HinweiseEine Coroutine kann eine
return- Anweisung mit beliebigen Rückgabewerten enthalten. So erhalten Sie diesen Wert:
result = await my_coro ()
Eine Coroutine kann durch Methoden begrenzt werden und muss mindestens eine
wait- Anweisung enthalten.
2.3 VerspätungenEs gibt zwei Möglichkeiten, Verzögerungen in Coroutinen zu organisieren. Für längere Verzögerungen und in Fällen, in denen die Dauer nicht genau sein muss, können Sie Folgendes verwenden:
async def foo( delay_secs , delay_ms ): await asyncio.sleep ( delay_secs ) print ( 'Hello' ) await asyncio.sleep_ms ( delay_ms )
Während solcher Verzögerungen führt der Scheduler andere Coroutinen aus. Dies kann zu zeitlicher Unsicherheit führen, da die aufrufende Coroutine nur gestartet wird, wenn die gerade laufende ausgeführt wird. Die Verzögerungszeit hängt vom Anwendungsentwickler ab, liegt jedoch wahrscheinlich in der Größenordnung von zehn oder hundert ms. Dies wird im Abschnitt Interaktion mit Hardwaregeräten (Abschnitt 6) näher erläutert.
Mit den
utime- Funktionen
sleep_ms und
sleep_us können sehr genaue Verzögerungen durchgeführt werden. Sie eignen sich am besten für kurze Verzögerungen, da der Scheduler während der Verzögerung keine anderen Coroutinen ausführen kann.
3.SyncOft muss die Synchronisation zwischen den Koroutinen sichergestellt werden. Ein häufiges Beispiel ist die Vermeidung der sogenannten "Race Conditions", wenn mehrere Coroutinen gleichzeitig Zugriff auf dieselbe Ressource benötigen. Ein Beispiel ist in
astests.py enthalten und wird in der
Dokumentation erläutert. Eine weitere Gefahr sind „Todesumarmungen“, wenn jede Koroutine darauf wartet, dass die andere abgeschlossen ist.
In einfachen Anwendungen kann die Synchronisierung mithilfe globaler Flags oder verwandter Variablen erfolgen. Ein eleganterer Ansatz ist die Verwendung von Synchronisationsklassen. Das Modul
asyn.py bietet Mikroimplementierungen der Klassen
Event, Barrier, Semaphore und
Conditios , die nur für die Verwendung mit
asyncio vorgesehen sind . Sie sind nicht
threadorientiert und sollten nicht mit dem
_thread- Modul oder dem Interrupt-Handler verwendet werden, sofern nicht anders angegeben. Die
Lock- Klasse ist ebenfalls implementiert, was eine Alternative zur offiziellen Implementierung darstellt.
Ein weiteres Synchronisationsproblem tritt bei Coroutine-Produzenten und Coroutine-Konsumenten auf. Ein Coroutine-Produzent generiert Daten, die ein Coroutine-Konsument verwendet. Zu diesem
Zweck stellt
asyncio die
Queue- Klasse bereit. Der Coroutine-Produzent stellt die Daten in die Warteschlange, während der Coroutine-Consumer auf die Fertigstellung wartet (wobei andere Operationen pünktlich geplant sind). Die
Queue- Klasse bietet Garantien zum Entfernen von Elementen in der Reihenfolge, in der sie empfangen wurden. Alternativ können Sie die
Barrier- Klasse verwenden, wenn die Producer-Coroutine warten muss, bis die Consumer-Coroutine bereit ist, auf die Daten zuzugreifen.
Eine kurze Übersicht über die Klassen finden Sie weiter unten. Weitere Details finden Sie in der
vollständigen Dokumentation .
3.1 SperreLock garantiert einen eindeutigen Zugriff auf eine gemeinsam genutzte Ressource. Im folgenden Codebeispiel wird eine Instanz der
Sperrklasse Lock erstellt , die an alle Clients übergeben wird, die auf die freigegebene Ressource zugreifen möchten. Jede Coroutine versucht, die Sperre zu erfassen und hält die Ausführung an, bis sie erfolgreich ist:
import uasyncio as asyncio from uasyncio.synchro import Lock async def task(i, lock): while 1: await lock.acquire() print("Acquired lock in task", i) await asyncio.sleep(0.5) lock.release() async def killer(): await asyncio.sleep(10) loop = asyncio.get_event_loop() lock = Lock()
3.1.1.Lock und TimeoutsZum Zeitpunkt des Schreibens (5. Januar 2018) ist die Entwicklung der Klasse
uasycio Lock noch nicht offiziell abgeschlossen. Wenn die Coroutine eine
Zeitüberschreitung aufweist (Abschnitt 5.2.2.) , Ist die Zeitüberschreitung unwirksam, wenn auf eine Sperre gewartet wird, wenn diese ausgelöst wird. Es wird kein
TimeoutError empfangen, bis es eine Sperre erhält. Gleiches gilt für das Abbrechen einer Aufgabe.
Das
asyn.py- Modul bietet die
Lock- Klasse, die in diesen Situationen funktioniert. Diese Implementierung der Klasse ist weniger effizient als die offizielle Klasse, unterstützt jedoch zusätzliche Schnittstellen gemäß der
CPython- Version, einschließlich der Verwendung des Kontextmanagers.
3.2 EreignisDas Ereignis gibt einer oder mehreren Koroutinen die Möglichkeit, eine Pause einzulegen, während eine andere ein Signal für ihre Fortsetzung gibt. Eine Instanz von
Event wird für alle Coroutinen verfügbar, die es verwenden:
import asyn event = asyn.Event ()
Eine Coroutine wartet auf ein Ereignis, indem sie ein
Warteereignis deklariert.
Danach wird die Ausführung unterbrochen, bis andere Coroutinen
event.set () deklarieren.
Vollständige Informationen .
Ein Problem kann auftreten, wenn
event.set () in einem Schleifenkonstrukt ausgegeben wird. Der Code muss warten, bis alle ausstehenden Objekte Zugriff auf das Ereignis haben, bevor er erneut festgelegt wird. Wenn ein
Coro ein Ereignis erwartet, kann dies erreicht werden, indem ein
Coro- Ereignis empfangen wird, das das Ereignis
löscht :
async def eventwait ( event ): await event event.clear()
Die Coroutine, die das Ereignis auslöst, überprüft, ob es gewartet wurde:
async def foo ( event ): while True :
Wenn mehrere
Coros auf die Synchronisation eines Ereignisses warten, kann das Problem mit dem Bestätigungsereignis behoben werden. Jeder
Coro benötigt ein eigenes Event.
async def eventwait ( , ack_event ): await event ack_event.set ()
Ein Beispiel hierfür finden Sie in der Funktion
event_test in der
Datei asyntest.py . Dies ist in den meisten Fällen umständlich. Selbst bei einem wartenden
Coro bietet die unten dargestellte
Barrier- Klasse einen einfacheren Ansatz.
Ein Ereignis kann auch ein Kommunikationsmittel zwischen dem Interrupt-Handler und
coro darstellen . Der Handler wartet die Hardware und setzt das Ereignis, das von
coro bereits im normalen Modus geprüft wird.
3.2.1 EreigniswerteDie
event.set () -Methode kann einen optionalen Datenwert eines beliebigen Typs annehmen.
Coro wartet auf ein Ereignis und kann es mit
event.value () abrufen . Beachten Sie, dass
event.clear () auf
None gesetzt wird . Eine typische Verwendung für die
Coro- Einstellung des Ereignisses ist die Ausgabe von
event.set (utime.ticks_ms ()) . Jeder
Coro, der auf ein Ereignis wartet, kann die aufgetretene Verzögerung ermitteln, um dies beispielsweise zu kompensieren.
3.3 BarriereFür die
Barrier- Klasse gibt es zwei Verwendungszwecke.
Erstens kann eine Coroutine ausgesetzt werden, bis eine oder mehrere andere Coroutinen abgeschlossen sind.
Zweitens können sich mehrere Koroutinen an einem bestimmten Punkt treffen. Beispielsweise können ein Produzent und ein Konsument an dem Punkt synchronisieren, an dem der Produzent über Daten verfügt, und der Konsument ist bereit, diese zu verwenden. Zum Zeitpunkt der Ausführung kann die
Barriere einen zusätzlichen Rückruf ausgeben, bevor die Barriere entfernt wird, und alle anstehenden Ereignisse können fortgesetzt werden.
Der Rückruf kann eine Funktion oder eine Coroutine sein. In den meisten Anwendungen wird die Funktion höchstwahrscheinlich verwendet: Es kann garantiert werden, dass sie vor Abschluss ausgeführt wird, bevor die Barriere entfernt wird.
Ein Beispiel ist die Funktion
barrier_test in
asyntest.py . Im Code-Snippet dieses Programms:
import asyn def callback(text): print(text) barrier = asyn.Barrier(3, callback, ('Synch',)) async def report(): for i in range(5): print('{} '.format(i), end='') await barrier
Mehrere Instanzen der
Berichtskoroutine drucken ihr Ergebnis und halten an, bis auch andere Instanzen abgeschlossen sind, und warten, bis die
Barriere fortgesetzt wird. An dieser Stelle wird ein Rückruf durchgeführt. Nach Fertigstellung wird die ursprüngliche Coroutine fortgesetzt.
3.4 SemaphorDas Semaphor begrenzt die Anzahl der Coroutinen, die auf die Ressource zugreifen können. Es kann verwendet werden, um die Anzahl der Instanzen einer bestimmten Coroutine zu begrenzen, die gleichzeitig ausgeführt werden können. Dies geschieht mit einem Zugriffszähler, der vom Konstruktor initialisiert und jedes Mal reduziert wird, wenn die Coroutine ein Semaphor empfängt.
Der einfachste Weg, es in einem Kontextmanager zu verwenden:
import asyn sema = asyn.Semaphore(3) async def foo(sema): async with sema:
Ein Beispiel ist die Funktion
semaphore_test in
asyntest.py .
3.4.1 ( Eingeschränktes ) SemaphorEs funktioniert ähnlich wie die
Semaphore- Klasse, außer dass ein
ValueError gesetzt wird , wenn die
release- Methode bewirkt, dass der Zugriffszähler seinen Anfangswert überschreitet.
3.5 WarteschlangeDie
Queue- Klasse wird vom offiziellen
uasycio verwaltet und das Beispielprogramm
aqtest.py demonstriert seine Verwendung. Die Warteschlange wird wie folgt erstellt:
from uasyncio.queues import Queue q = Queue ()
Eine typische Hersteller-Coroutine kann wie folgt arbeiten:
async def producer(q): while True: result = await slow_process()
und die Consumer Coroutine kann wie folgt arbeiten:
async def consumer(q): while True: result = await(q.get())
Die
Queue- Klasse bietet erhebliche zusätzliche Funktionen, wenn die Größe der Warteschlangen begrenzt und der Status abgefragt werden kann. Das Verhalten mit einer leeren Warteschlange (wenn die Größe begrenzt ist) und das Verhalten mit einer vollen Warteschlange können gesteuert werden.
Die Dokumentation dazu befindet sich im Code.3.6 Andere SynchronisationsklassenDie Bibliothek asyn.py bietet eine Mikroimplementierung einiger anderer Funktionen von CPython .Mit der Condition- Klasse kann eine Coroutine andere Coroutines benachrichtigen, die auf eine gesperrte Ressource warten. Nach Erhalt der Benachrichtigung erhalten sie Zugriff auf die Ressource und werden freigeschaltet. Eine Benachrichtigungskoroutine kann die Anzahl der zu benachrichtigenden Koroutinen begrenzen.Mit der Gather- Klasse können Sie eine Liste von Coroutinen ausführen. Nach Abschluss des letzteren wird eine Liste der Ergebnisse zurückgegeben. Diese "Mikro" -Implementierung verwendet eine andere Syntax. Zeitüberschreitungen können auf jede der Coroutinen angewendet werden.4 Entwickeln von Klassen für AsyncioIm Rahmen der Entwicklung von Gerätetreibern soll sichergestellt werden, dass diese nicht blockieren. Ein Coroutine-Treiber muss sicherstellen, dass andere Coroutinen ausgeführt werden, während der Treiber darauf wartet, dass das Gerät Hardwarevorgänge ausführt. Beispielsweise sollte eine Task, die auf Daten wartet, die in UART eingehen, oder ein Benutzer, der eine Taste drückt, ermöglichen, dass andere Ereignisse geplant werden, bis das Ereignis eintritt.4.1 Klassen mit Warten auf Warten Eine Coroutinekann die Ausführung anhalten, während sie auf ein wartendes Objekt wartet . Unter CPython benutzerdefinierte Klasse awaitable durch die Implementierung einer speziellen Methode erstellt __await__was der Generator zurückgibt. Die erwartete Klasse wird wie folgt verwendet: import uasyncio as asyncio class Foo(): def __await__(self): for n in range(5): print('__await__ called') yield from asyncio.sleep(1)
Derzeit MicroPython nicht unterstützen __await__ ( Ausgabe # 2678 ) und für die Lösung verwendet werden soll __iter__ . Die Zeichenfolge __iter__ = __await__ bietet Portabilität zwischen CPython und MicroPython . Code - Beispiele finden Sie in den Klassen Ereignis, Barrier, Cancellable, Zustand in asyn.py .4.1.1 Verwendung in KontextmanagernErwartete Objekte können in synchronen oder asynchronen Kontextmanagern verwendet werden, wobei die erforderlichen speziellen Methoden bereitgestellt werden. Syntax: with await awaitable as a:
Um dies zu erreichen, muss sich der __await__- Generator selbst zurückgeben . Dies wird an jede Variable in der as- Klausel übergeben und ermöglicht auch die Verwendung spezieller Methoden. Siehe asyn.Condition und asyntest.condition_test, in denen die von der Condition- Klasse verwendeten Funktionen warten und in einem synchronen Kontext-Manager verwendet werden können.4.1.2 Await Koroutine inSprache Python erfordert __await__ die Generatorfunktion war. In MicroPython sind die Generatoren und Coroutinen identisch, daher besteht die Lösung darin, die Ausbeute aus coro (args) zu verwenden .Der Zweck dieses Handbuchs besteht darin, Code anzubieten, der auf CPython 3.5 oder höher portierbar ist . In CPython haben Generatoren und Coroutinen unterschiedliche Bedeutungen. In CPython verfügt eine Coroutine über eine spezielle Methode __await__ , die der Generator abruft. Dies ist portabel: up = False
Beachten Sie, dass __await__, yield from asyncio.sleep (1) von CPython erlaubt ist . Ich verstehe immer noch nicht, wie dies erreicht wird.4.2 Asynchrone IteratorenAsynchrone Iteratoren bieten die Möglichkeit, eine endliche oder unendliche Folge von Werten zurückzugeben. Sie können zum Abrufen von sequentiellen Datenelementen verwendet werden, wenn diese von einem schreibgeschützten Gerät stammen. Ein asynchroner Iterator ruft bei seiner nächsten Methode asynchronen Code auf . Die Klasse muss die folgenden Anforderungen erfüllen:- Es gibt eine __aiter__- Methode, die in async def definiert ist und einen asynchronen Iterator zurückgibt .
- Es gibt eine __anext__- Methode , die selbst eine Coroutine ist, dh über async def definiert ist und mindestens eine wait- Anweisung enthält . Um die Iteration zu stoppen, muss eine StopAsyncIteration- Ausnahme ausgelöst werden .
Serielle Werte werden mit Async wie folgt abgerufen : class AsyncIterable: def __init__(self): self.data = (1, 2, 3, 4, 5) self.index = 0 async def __aiter__(self): return self async def __anext__(self): data = await self.fetch_data() if data: return data else: raise StopAsyncIteration async def fetch_data(self): await asyncio.sleep(0.1)
4.3 Asynchrone KontextmanagerKlassen können so entworfen werden, dass sie asynchrone Kontextmanager unterstützen, bei denen es sich um Co-Programme handelt. Ein Beispiel ist ein Klasse - Verschluss , wie oben beschrieben. Es verfügt über die Coroutine __aenter__ , die für den asynchronen Betrieb logisch erforderlich ist. Um das asynchrone Protokoll des Kontextmanagers zu unterstützen , muss die Methode __aexit__ ebenfalls eine Coroutine sein. Dies wird durch Einschließen von await asyncio.sleep (0) erreicht . Auf solche Klassen kann innerhalb einer Coroutine mit der folgenden Syntax zugegriffen werden: async def bar ( lock ): async with lock: print ( « bar » )
Wie bei normalen Kontextmanagern wird die Exit-Methode garantiert aufgerufen, wenn der Kontextmanager seine Arbeit wie gewohnt und über eine Ausnahme abgeschlossen hat. Um dieses Ziel zu erreichen, werden spezielle Methoden __aenter__ und __aexit__ verwendet , die als Coroutinen definiert werden müssen, die auf eine andere Coroutine oder ein anderes wartbares Objekt warten . Dieses Beispiel stammt aus der Lock- Klasse : async def __aenter__(self): await self.acquire()
Wenn async with eine Klausel as variable enthält , erhält die Variable den von __aenter__ zurückgegebenen Wert .Um ein korrektes Verhalten zu gewährleisten, muss die Firmware V1.9.10 oder höher sein.5. Ausnahmen von Zeitüberschreitungen und aufgrund des Abbruchs von AufgabenDiese Themen stehen in Zusammenhang: uasyncio umfasst das Abbrechen von Aufgaben und das Anwenden einer Zeitüberschreitung auf eine Aufgabe, wobei eine Ausnahme für die Aufgabe auf besondere Weise ausgelöst wird .5.1 Ausnahmen(exeption), , , . , . , , , . , , ,
loop.create_task() .
throw close , .
uasyncio , , , , .
Das obige Beispiel veranschaulicht diese Situation. Wenn es erlaubt ist, bis zum Ende zu arbeiten, funktioniert es wie erwartet. import uasyncio as asyncio async def foo(): await asyncio.sleep(3) print('About to throw exception.') 1/0 async def bar(): try: await foo() except ZeroDivisionError: print('foo - 0')
Das Ausgeben einer Tastaturunterbrechung führt jedoch dazu, dass die Ausnahme in die Ereignisschleife eintritt. Dies liegt daran, dass die Ausführung von uasyncio.sleep an die Ereignisschleife übergeben wird. Daher müssen Anwendungen, die als Reaktion auf eine Tastaturunterbrechung einen eindeutigen Code benötigen, eine Ausnahme auf der Ebene der Ereignisschleife abfangen.5.2 Stornierung und TimeoutsWie oben erwähnt, sind diese Funktionen arbeiten, eine Ausnahme für eine bestimmte Aufgabe zu verursachen, eine spezielle Methode mit MicroPython Koroutine pend_throw . Wie es funktioniert, hängt von der Version ab. In der offiziellen Version 2.0 von uasyncio wird eine Ausnahme erst bei der nächsten geplanten Aufgabe verarbeitet. Dies führt zu einer Verzögerung, wenn die Aufgabe Schlaf erwartetEingabe-Ausgabe Zeitüberschreitungen können über den nominalen Zeitraum hinausgehen. Die Aufgabe zum Rückgängigmachen anderer Aufgaben kann nicht bestimmen, wann das Rückgängigmachen abgeschlossen ist.Derzeit gibt es eine Problemumgehung und zwei Lösungen.- Umgehung : In der asyn- Bibliothek können Sie darauf warten, dass Aufgaben oder Aufgabengruppen abgebrochen werden. Siehe Abbrechen eines Jobs (Abschnitt 5.2.1.).
- Die Paul Sokolovsky-Bibliothek stellt uasyncio v2.4 zur Verfügung , erfordert jedoch die Pycopy- Firmware .
- Fast_io Bibliothek uasyncio löst dieses Problem in dem Python (weniger elegante ArtWeise) und offizielle Firmware läuft.
Die hier verwendete Ausnahmehierarchie lautet Exception-CanceledError-TimeoutError .5.2.1 Abbrechen eines Jobsuasyncio bietet eine Abbruchfunktion (Coro) . Dies funktioniert, indem eine Ausnahme ausgelöst wird , um die Coroutine pend_throw zu verwenden . Es funktioniert auch mit verschachtelten Coroutinen. Verwendung ist wie folgt: async def foo(): while True:
Wenn dieses Beispiel unter uasyncio v2.0 ausgeführt wird und der Balken cancel zurückgibt , wird er erst beim nächsten geplanten Foo wirksam, und es kann zu einer Verzögerung von bis zu 10 Sekunden kommen, wenn foo annulliert wird . Eine weitere Verzögerungsquelle tritt auf, wenn foo auf E / A wartet. Wo immer die Verzögerung auftritt, kann der Balken nicht feststellen, ob foo annulliert wurde. Es ist in einigen Anwendungsfällen von Bedeutung.Bei Verwendung der Bibliotheken Paul Sokolovsky oder fast_io ist es ausreichend, sleep (0) zu verwenden: async def foo(): while True:
Dies funktioniert auch in uasyncio v2.0, wenn foo (und alle ausstehenden Coroutine foo ) niemals den Schlaf wiedergegeben haben und nicht auf I / O gewartet haben.Ein Verhalten, das die Unachtsamkeit überraschen kann, tritt auf, wenn erwartet wird, dass eine von create_task ausgeführte Coroutine im Standby-Modus abgebrochen wird . Betrachten Sie dieses Snippet: async def foo(): while True:
Wenn foo abgebrochen wird, wird es aus der Scheduler-Warteschlange entfernt. Da es keine return- Anweisung gibt, wird die aufrufende Prozedur foo_runner nie fortgesetzt . Es wird empfohlen, dass Sie die Ausnahme immer im äußersten Bereich der rückgängig zu machenden Funktion abfangen: async def foo(): try: while True: await asyncio.sleep(10) await my_coro except asyncio.CancelledError: return
In diesem Fall muss my_coro die Ausnahme nicht abfangen, da sie an den aufrufenden Kanal weitergegeben und dort erfasst wird.Hinweis
Es ist verboten, Close- oder Throw- Methoden von Coroutinen zu verwenden, wenn Coroutinen außerhalb des Schedulers verwendet werden. Dies untergräbt den Scheduler und zwingt die Coroutine, Code auszuführen, auch wenn er nicht geplant ist. Dies kann unerwünschte Folgen haben.5.2.2 Coroutinen mit TimeoutsTimeouts werden mit den uasyncio- Methoden .wait_for () und .wait_for_ms () implementiert . Sie nehmen Coroutine und Latenz in Sekunden bzw. ms als Argumente. Wenn das Timeout abläuft, wird mit pend_throw ein TimeoutError in die Coroutine geworfen. Diese Ausnahme muss entweder vom Benutzer oder vom Anrufer abgefangen werden. Dies ist aus dem oben beschriebenen Grund erforderlich: Wenn das Timeout abläuft, wird es abgebrochen. Wenn der Fehler nicht abgefangen und zurückgegeben wird, kann der Aufrufer nur die Ausnahme selbst abfangen.Wo die Ausnahme von der Coroutine abgefangen wurde, hatte ich unklare Fehler, wenn die Ausnahme nicht im äußeren Bereich abgefangen wurde, wie unten gezeigt: import uasyncio as asyncio async def forever(): try: print('Starting') while True: await asyncio.sleep_ms(300) print('Got here') except asyncio.TimeoutError: print('Got timeout')
Alternativ können Sie die aufrufende Funktion abfangen: import uasyncio as asyncio async def forever(): print('Starting') while True: await asyncio.sleep_ms(300) print('Got here') async def foo(): try: await asyncio.wait_for(forever(), 5) except asyncio.TimeoutError: pass print('Timeout elapsed.') await asyncio.sleep(2) loop = asyncio.get_event_loop() loop.run_until_complete(foo())
Hinweis für Uasyncio v2.0 .Dies gilt nicht für die Bibliotheken Paul Sokolovsky oder fast_io .Wenn die Coroutine startet und mit einer langen Verzögerung t auf asynchronen Schlaf (t) wartet , wird die Coroutine nicht neu gestartet , bis t abläuft . Wenn die Zeitüberschreitung vor dem Ende des Ruhezustands abgelaufen ist , tritt ein TimeoutError auf, wenn die Coroutine neu geladen wird - d. H. wenn t abläuft . In Echtzeit und aus Sicht des Anrufers wird seine TimeoutError- Antwort verzögert.Wenn dies für die Anwendung wichtig ist, erstellen Sie eine lange Verzögerung, während Sie auf eine kurze Verzögerung in der Schleife warten. Coroutineasyn.sleep unterstützt dies.6 Interaktion mit GerätenDie Grundlage für die Interaktion zwischen uasyncio und externen asynchronen Ereignissen ist die Abfrage. Hardware, die eine schnelle Reaktion erfordert, verwendet möglicherweise einen Interrupt. Die Interaktion zwischen der Interruptroutine (ISR) und der Benutzer-Coroutine basiert jedoch auf Umfragen. Beispielsweise kann ein ISR ein Ereignis aufrufen oder ein globales Flag setzen, während eine Coroutine, die auf ein Ergebnis wartet, jedes Mal ein Objekt abfragt, wenn eine Anforderung geplant ist.Die Abfrage kann auf zwei Arten erfolgen, explizit oder implizit. Letzteres erfolgt über Stream I / OEin Mechanismus, der für das Streaming von Geräten wie UART und Sockets entwickelt wurde . In der einfachsten expliziten Abfrage kann der folgende Code bestehen: async def poll_my_device(): global my_flag
Anstelle eines globalen Flags können Sie eine Instanzvariable der Event- Klasse oder eine Instanz einer Klasse verwenden, die wait verwendet . Eine explizite Umfrage wird unten diskutiert.Implizites Polling besteht darin, einen Treiber zu entwickeln, der als Streaming-E / A-Gerät fungiert, z. B. als UART- oder Streaming-E / A- Socket , der Geräte mit dem Python- System select.poll abfragt : Da das Polling in C ausgeführt wird, ist es schneller und effizienter als explizite Umfrage. Die Verwendung von Stream-E / A wird in Abschnitt 6.3 erläutert. Aufgrund seiner Effektivität bietet implizites Polling den schnellsten E / A-Gerätetreibern einen Vorteil: Streaming-Treiber können für viele Geräte erstellt werden, die normalerweise nicht als Streaming-Geräte betrachtet werden. Dies wird in Abschnitt 6.4 näher erläutert.6.1 SynchronisierungsproblemeSowohl explizite als auch implizite Umfragen basieren derzeit auf einer zyklischen Planung. Angenommen, E / A funktioniert gleichzeitig mit N benutzerdefinierten Coroutinen, von denen jede ohne Verzögerung ausgeführt wird. Wenn die E / A bedient wird, wird sie abgefragt, sobald alle Benutzervorgänge geplant sind. Die geschätzte Verzögerung sollte bei der Planung berücksichtigt werden. E / A-Kanäle müssen möglicherweise gepuffert werden, wobei ISR-Wartungsgeräte in Echtzeit Puffer und Coroutinen verwenden, um die Puffer zu einem langsameren Zeitpunkt zu füllen oder freizugeben.Man muss auch die Möglichkeit eines Überschreitens in Betracht ziehen: Dies ist der Fall, wenn etwas, das von der Coroutine abgefragt wird, mehr als einmal vorkommt, bevor es tatsächlich von der Coroutine geplant wird.Ein weiteres Zeitproblem ist die Latenzgenauigkeit. Wenn die Coroutine Probleme hat await asyncio.sleep_ms ( t )
Der Scheduler garantiert, dass die Ausführung für mindestens t ms unterbrochen wird. Die tatsächliche Verzögerung kann größer als t sein, was von der aktuellen Systemlast abhängt. Wenn zu diesem Zeitpunkt andere Coroutinen auf die Beendigung von Verzögerungen ungleich Null warten, wird die Ausführung der nächsten Zeile sofort eingeplant. Wenn jedoch auch andere Coroutinen auf die Ausführung warten (entweder weil sie eine Verzögerung von Null ausgegeben haben oder weil ihre Zeit ebenfalls abgelaufen ist), ist möglicherweise eine frühere Ausführung geplant. Dies führt eine Synchronisationsunsicherheit in die Funktionen sleep () und sleep_ms () ein . Der Worst-Case-Wert für diesen Überlauf kann berechnet werden, indem die Laufzeitwerte aller dieser Coroutinen summiert werden, um die Worst-Case-Übertragungszeit an den Scheduler zu bestimmen.Die fast_io- Version von uasyncio bietet in diesem Kontext eine Möglichkeit, um sicherzustellen, dass Streaming-E / A bei jeder Iteration des Schedulers abgefragt werden. Es wird gehofft, dass das offizielle uasyncio die entsprechenden Änderungen rechtzeitig akzeptiert.6.2 Abfragen von Geräten mit CoroutinenDies ist ein einfacher Ansatz, der am besten für Geräte geeignet ist, die mit einer relativ geringen Geschwindigkeit abgefragt werden können. Dies ist hauptsächlich auf die Tatsache zurückzuführen, dass das Abrufen mit einem kurzen (oder Null-) Abfrageintervall dazu führen kann, dass die Coroutine mehr Prozessorzeit verbraucht, als für das Fallen in das Intervall wünschenswert ist.Das apoll.py- Beispiel veranschaulicht diesen Ansatz durch Abfragen des Pyboard- Beschleunigungsmessersmit einem Intervall von 100 ms. Es führt eine einfache Filterung durch, um Rauschen zu ignorieren, und druckt alle zwei Sekunden eine Meldung, wenn keine Bewegung auftritt.Das Beispiel aswitch.py enthält Treiber für Schalter und Tastengeräte .Ein Beispieltreiber für ein Gerät, das lesen und schreiben kann, ist unten dargestellt. Zum leichteren Testen emuliert Pyboard UART 4 ein bedingtes Gerät. Der Treiber implementiert die RecordOrientedUart- Klassewobei Daten in Datensätzen variabler Länge geliefert werden, die aus Byte-Instanzen bestehen. Das Objekt fügt vor dem Senden ein Trennzeichen hinzu und puffert die eingehenden Daten, bis ein hinzugefügtes Trennzeichen empfangen wird. Dies ist nur eine Demo und eine ineffiziente Art, UART im Vergleich zu Streaming Input / Output zu verwenden.Um die asynchrone Übertragung zu demonstrieren, wird davon ausgegangen, dass das emulierte Gerät über ein Mittel verfügt, mit dem überprüft werden kann, ob die Übertragung abgeschlossen ist und die Anwendung eine Wartezeit erfordert. In diesem Beispiel ist keine der Annahmen wahr, aber der Code täuscht sie vor, indem er auf asyncio.sleep (0.1) wartet .Vergessen Sie zu Beginn nicht, die Ausgänge des Pyboard X1 und X2 (UART Txd und Rxd) anzuschließen. import uasyncio as asyncio from pyb import UART class RecordOrientedUart(): DELIMITER = b'\0' def __init__(self): self.uart = UART(4, 9600) self.data = b'' def __iter__(self):
6.3 Verwenden des Streaming-Mechanismus ( Stream )Das Beispiel zeigt die gleichzeitige E / A auf einem einzelnen UART Pyboard- Mikroprozessor .Verbinden Sie zum Starten die Ausgänge des Pyboard X1 und X2 (UART Txd und Rxd) import uasyncio as asyncio from pyb import UART uart = UART(4, 9600) async def sender(): swriter = asyncio.StreamWriter(uart, {}) while True: await swriter.awrite('Hello uart\n') await asyncio.sleep(2) async def receiver(): sreader = asyncio.StreamReader(uart) while True: res = await sreader.readline() print('Received', res) loop = asyncio.get_event_loop() loop.create_task(sender()) loop.create_task(receiver()) loop.run_forever()
Der unterstützende Code befindet sich in __init__.py in der uasyncio- Bibliothek. Der Mechanismus funktioniert, weil der Gerätetreiber (in C geschrieben ) die folgenden Methoden implementiert: ioctl, read, readline und write . In Abschnitt 6.4: Schreiben eines Streaming-Gerätetreibers erfahren Sie, wie solche Treiber in Python geschrieben werden können .UART . - , . , ; , . , UART , , . , UART, . , .
6.3.1 UARTauart_hd.pyveranschaulicht ein Kommunikationsverfahren mit einem Halbduplex-Gerät, beispielsweise einem Gerät, das auf den AT-Modem-Befehlssatz reagiert. Halbduplex bedeutet, dass das Gerät niemals unerwünschte Daten sendet: Die Übertragung erfolgt immer auf einen empfangenen Befehl des Masters hin.Das Gerät wird durch Ausführen eines Tests auf einem Pyboard mit zwei Kabelverbindungen emuliert .Das (sehr vereinfachte) emulierte Gerät reagiert auf jeden Befehl, indem es vier Datenzeilen mit einer Pause dazwischen sendet, um eine langsame Verarbeitung zu simulieren.Der Assistent sendet einen Befehl, weiß jedoch nicht im Voraus, wie viele Datenzeilen zurückgegeben werden. Es startet einen Neustart-Timer, der jedes Mal neu startet, wenn eine Leitung empfangen wird. Nach Ablauf des Timers wird davon ausgegangen, dass das Gerät die Übertragung abgeschlossen hat und eine Liste der empfangenen Leitungen zurückgegeben wird.Es wird auch ein Geräteausfall beschrieben, bei dem eine Übertragung übersprungen wird, bevor auf eine Antwort gewartet wird. Nach dem Timeout wird eine leere Liste zurückgegeben. Weitere Details finden Sie in den Codekommentaren.6.4 Entwicklung von Streaming (Treiber - Stream ) -EinheitStrom Ein- / Ausgabemechanismus ( Stream - E / A ) zur Steuerung des Betriebes von Streaming - E / A - Geräten wie UART und Buchsen ( Steckdose) Der Mechanismus kann von Treibern jedes regelmäßig abgefragten Geräts verwendet werden, indem er an den Scheduler delegiert wird, der select verwendet, um die Bereitschaft aller Geräte in der Warteschlange abzufragen. Dies ist effizienter als die Ausführung mehrerer Coroutine-Operationen, von denen jede das Gerät abfragt , zum Teil, weil select in C geschrieben ist , und auch, weil die Coroutine, die die Abfrage durchführt, verzögert wird, bis das abgefragte Objekt einen Bereitschaftszustand zurückgibt.Ein Gerätetreiber, der den Streaming-Ein- / Ausgabemechanismus bedienen kann, sollte vorzugsweise die Methoden StreamReader, StreamWriter unterstützen. Ein lesbares Gerät muss mindestens eine der folgenden Methoden bereitstellen. Bitte beachten Sie, dass dies synchrone Methoden sind. Die ioctl- Methode (siehe unten) stellt sicher, dass sie nur aufgerufen werden, wenn Daten verfügbar sind. Methoden sollten so schnell wie möglich zurückgegeben werden, wobei so viele Daten wie möglich verwendet werden.readline () Gibt so viele Zeichen wie möglich bis zu einem beliebigen Zeilenvorschub zurück. Erforderlich bei Verwendung von StreamReader.readline ()read (n) Gibt so viele Zeichen wie möglich zurück, jedoch nicht mehr als n . Erforderlich, wenn StreamReader.read () oder StreamReader.readexactly () verwendet wirdDer erstellte Treiber sollte die folgende synchrone Methode mit sofortiger Rückgabe bereitstellen:Schreiben Sie mit den Argumenten buf, off, sz .Wo:
buf ist ein Puffer zum Schreiben.off - Offset zum Puffer des ersten zu schreibenden Zeichens.sz - Die angeforderte Anzahl der zu schreibenden Zeichen.Der Rückgabewert ist die Anzahl der tatsächlich geschriebenen Zeichen (möglicherweise 1, wenn das Gerät langsam ist).Die ioctl- Methode stellt sicher, dass sie nur aufgerufen wird, wenn das Gerät bereit ist, Daten zu empfangen.Alle Geräte müssen eine ioctl- Methode bereitstellen, mit der Geräte abgefragt werden , um ihren Verfügbarkeitsstatus zu ermitteln. Ein typisches Beispiel für einen Lese- / Schreibtreiber: import io MP_STREAM_POLL_RD = const(1) MP_STREAM_POLL_WR = const(4) MP_STREAM_POLL = const(3) MP_STREAM_ERROR = const(-1) class MyIO(io.IOBase):
Im Folgenden wird die Wartezeit der MillisecTimer- Klasse beschrieben : import uasyncio as asyncio import utime import io MP_STREAM_POLL_RD = const(1) MP_STREAM_POLL = const(3) MP_STREAM_ERROR = const(-1) class MillisecTimer(io.IOBase): def __init__(self): self.end = 0 self.sreader = asyncio.StreamReader(self) def __iter__(self): await self.sreader.readline() def __call__(self, ms): self.end = utime.ticks_add(utime.ticks_ms(), ms) return self def readline(self): return b'\n' def ioctl(self, req, arg): ret = MP_STREAM_ERROR if req == MP_STREAM_POLL: ret = 0 if arg & MP_STREAM_POLL_RD: if utime.ticks_diff(utime.ticks_ms(), self.end) >= 0: ret |= MP_STREAM_POLL_RD return ret
welches wie folgt verwendet werden kann: async def timer_test ( n ): timer = ms_timer.MillisecTimer () await timer ( 30 )
Gegenüber dem offiziellen uasyncio bietet eine solche Implementierung keine Vorteile gegenüber dem asyncio.sleep_ms () . Die Verwendung von fast_io bietet im normalen Verwendungsmuster wesentlich genauere Verzögerungen, wenn Coroutinen eine Verzögerung von Null erwarten.Sie können die E / A-Planung verwenden, um ein Ereignis einem Rückruf zuzuordnen. Dies ist effizienter als der Abrufzyklus , da der Abruf erst geplant wird, wenn ioctl bereit ist. Als nächstes wird ein Rückruf ausgeführt, wenn der Rückruf den Zustand ändert. import uasyncio as asyncio import io MP_STREAM_POLL_RD = const(1) MP_STREAM_POLL = const(3) MP_STREAM_ERROR = const(-1) class PinCall(io.IOBase): def __init__(self, pin, *, cb_rise=None, cbr_args=(), cb_fall=None, cbf_args=()): self.pin = pin self.cb_rise = cb_rise self.cbr_args = cbr_args self.cb_fall = cb_fall self.cbf_args = cbf_args self.pinval = pin.value() self.sreader = asyncio.StreamReader(self) loop = asyncio.get_event_loop() loop.create_task(self.run()) async def run(self): while True: await self.sreader.read(1) def read(self, _): v = self.pinval if v and self.cb_rise is not None: self.cb_rise(*self.cbr_args) return b'\n' if not v and self.cb_fall is not None: self.cb_fall(*self.cbf_args) return b'\n' def ioctl(self, req, arg): ret = MP_STREAM_ERROR if req == MP_STREAM_POLL: ret = 0 if arg & MP_STREAM_POLL_RD: v = self.pin.value() if v != self.pinval: self.pinval = v ret = MP_STREAM_POLL_RD return ret
Und wieder - auf der offiziellen Uasyncio kann die Verzögerung hoch sein. Je nach Anwendungsdesign ist die fast_io- Version möglicherweise effizienter.Die iorw.py-Demo zeigt ein vollständiges Beispiel. Bitte beachten Sie, dass zum Zeitpunkt des Schreibens des Artikels im offiziellen uasyncio ein Fehler vorliegt, aufgrund dessen dies nicht funktioniert . Es gibt zwei Lösungen. Die Problemumgehung besteht darin, zwei separate Treiber zu schreiben, einen nur zum Lesen und einen nur zum Schreiben. Die zweite Möglichkeit ist die Verwendung von fast_io , um dieses Problem zu lösen.Im offiziellen Uasyncio ist die Eingabe / Ausgabe ziemlich selten geplant .6.5 Vollständiges Beispiel: aremote.pyDer Treiber ist zum Empfangen / Dekodieren von Signalen von einer Infrarot-Fernbedienung ausgelegt. Der aremote.py- Treiber selbst . Die folgenden Hinweise sind für die Verwendung von Asyncio von Bedeutung .Die Unterbrechung des Kontakts zeichnet den Zeitpunkt des Zustandswechsels (in Mikrosekunden) auf und setzt das Ereignis, wobei der Zeitpunkt des ersten Zustandswechsels übersprungen wird. Die Coroutine wartet auf ein Ereignis, meldet die Dauer des Datenpakets und decodiert dann die gespeicherten Daten, bevor sie den vom Benutzer angegebenen Rückruf aufruft.Durchdie Übergabe der Zeit an eine Ereignisinstanz kann die Coroutinebeim Festlegen der Verzögerungszeitjede Asynchronitätsverzögerung ausgleichen.6.6 Umgebungssensor HTU21DDer HTU21D-Chiptreiber bietet genaue Temperatur- und Feuchtigkeitsmessungen.Der Chip benötigt ca. 120 ms, um beide Datenelemente zu empfangen. Der Treiber arbeitet asynchron und initiiert den Empfang und die Verwendung von wait asyncio.sleep (t), bevor Daten gelesen werden. Er aktualisiert die Temperatur- und Feuchtigkeitsvariablen, auf die jederzeit zugegriffen werden kann. Dadurch können andere Coroutinen gestartet werden, während der Chiptreiber ausgeführt wird.7. Tipps und Tricks7.1 Das Programm friert ein Das Einfrieren erfolgtnormalerweise, weil die Aufgabe ohne Erlaubnis blockiert wird. Dies führt zum Einfrieren des gesamten Systems. Bei der Entwicklung ist es nützlich, eine Coroutine zu haben, die die eingebaute LED regelmäßig einschaltet. Dadurch wird bestätigt, dass der Scheduler noch ausgeführt wird.7.2 uasyncio speichert StatusWenn Sie Programme mit uasyncio in REPL starten , führen Sie zwischen den Starts einen Soft-Reset (Strg-D) durch. Aufgrund der Tatsache, dass uasyncio den Status zwischen den Starts beibehält, kann es beim nächsten Start zu unvorhersehbarem Verhalten kommen.7.3 SpeicherbereinigungSie können eine Coroutine ausführen, indem Sie zuerst import gc angeben : gc.collect () gc.treshold ( gc.mem_free () // 4 + gc.mem_alloc ())
Der Zweck hierfür wird hier im Abschnitt Heap erläutert .7.4 TestenEs wird empfohlen, sicherzustellen, dass der Gerätetreiber die Kontrolle behält, wenn dies erforderlich ist. Führen Sie dazu eine oder mehrere Kopien von fiktiven Coroutinen aus, die den Nachrichtendruckzyklus starten, und überprüfen Sie, ob der Treiber in den folgenden Zeiträumen im Standby-Modus ausgeführt wird: async def rr(n): while True: print('Roundrobin ', n) await asyncio.sleep(0)
Als ein Beispiel die Art der Gefahr , die im obigen Beispiel auftreten kann RecordOrientedUart __await__ wurde Methode ursprünglich geschrieben als: def __await__(self): data = b'' while not data.endswith(self.DELIMITER): while not self.uart.any(): yield from asyncio.sleep(0) data = b''.join((data, self.uart.read(self.uart.any()))) self.data = data
Infolgedessen wird die Ausführung so lange gestreckt, bis der gesamte Datensatz empfangen wurde. Außerdem gibt uart.any () immer eine von Null verschiedene Anzahl empfangener Zeichen zurück. Zum Zeitpunkt des Anrufs sind möglicherweise bereits alle Zeichen eingegangen. Diese Situation kann mit einer externen Schleife gelöst werden: def __await__(self): data = b'' while not data.endswith(self.DELIMITER): yield from asyncio.sleep(0)
Es kann erwähnenswert sein, dass dieser Fehler nicht offensichtlich gewesen wäre, wenn die Daten mit einer geringeren Geschwindigkeit an den UART gesendet worden wären, anstatt einen Rückkopplungstest zu verwenden. Willkommen zu den Freuden der Echtzeitprogrammierung.7.5 Häufiger FehlerWenn eine Funktion oder Methode von async def definiert und anschließend wie ein regulärer (synchroner) Aufruf aufgerufen wird, zeigt MicroPython keine Fehlermeldung an. Dies ist beabsichtigt. Normalerweise führt dies dazu, dass das Programm im Hintergrund nicht richtig funktioniert: async def foo():
Ich habe einen Vorschlag , der vorschlägt, die Situation in Option 1 mit fast_io zu beheben .Das Modul check_async_code.py versucht, Fälle von zweifelhafter Verwendung von Coroutinen zu erkennen. Es ist in Python3 geschrieben und für die Arbeit auf einem PC ausgelegt. Wird in Skripten verwendet, die gemäß den in diesem Handbuch beschriebenen Richtlinien mit Coroutinen geschrieben wurden, die mit async def deklariert wurden . Das Modul hat ein Argument, den Pfad zu der Quelldatei MicroPython (oder --help).Bitte beachten Sie, dass es etwas unhöflich ist und in einer syntaktisch korrekten Datei verwendet werden soll, die nicht standardmäßig gestartet wird. Verwenden Sie ein Tool wie pylint zur allgemeinen Syntaxprüfung (bei pylint tritt dieser Fehler derzeit nicht auf).Das Skript erzeugt falsche Positive. Koroutinen sind laut Plan Objekte der ersten Ebene, sie können auf Funktionen übertragen und in Datenstrukturen abgelegt werden. Abhängig von der Logik des Programms können Sie die Funktion oder das Ergebnis ihrer Ausführung speichern. Das Skript kann die Absicht nicht bestimmen. Es zielt darauf ab, Fälle zu ignorieren, die korrekt erscheinen, wenn andere zu berücksichtigende Fälle identifiziert werden. Angenommen, foo, in der die Coroutine als asynchron def deklariert ist : loop.run_until_complete(foo())
Ich finde es nützlich, aber Verbesserungen sind immer willkommen.7.6 Programmieren mit Steckdosen ( Steckdosen )Es gibt zwei grundlegende Ansätze zur Programmierung Steckdosen uasyncio . Standardmäßig werden Sockets gesperrt, bis der angegebene Lese- oder Schreibvorgang abgeschlossen ist. Uasyncio unterstützt das Sperren von Sockets mit select.poll , um zu verhindern, dass der Scheduler sie blockiert. In den meisten Fällen ist dieser Mechanismus am einfachsten zu bedienen. Ein Beispiel für Client- und Server-Code finden Sie im Verzeichnis client_server . Der Benutzer verwendet die Anwendung select.poll , indem er den Server-Socket explizit abfragt.Client-Sockets verwenden es implizit in dem Sinne, dass die uasyncio- Streaming-Engine es direkt verwendet.Bitte beachten Sie, dass socket.getaddrinfo derzeit gesperrt ist. Die Zeit im Beispielcode ist minimal, aber wenn eine DNS-Suche erforderlich ist, kann der Sperrzeitraum erheblich sein.Ein zweiter Ansatz zur Socket-Programmierung ist die Verwendung nicht blockierender Sockets. Dies erhöht die Komplexität, ist jedoch in einigen Anwendungen erforderlich, insbesondere wenn die Verbindung über WLAN erfolgt (siehe unten).Zum Zeitpunkt dieser Veröffentlichung (März 2019) befand sich die TLS-Unterstützung für nicht blockierende Sockets in der Entwicklung. Ihr genauer Status ist mir unbekannt.Die Verwendung von nicht blockierenden Steckdosen erfordert viel Liebe zum Detail. Wenn aufgrund der Serverlatenz nicht blockierende Lesevorgänge auftreten, kann nicht garantiert werden, dass alle (oder einige) der angeforderten Daten zurückgegeben werden. Ebenso können Einträge nicht vollständig sein.Daher müssen asynchrone Lese- und Schreibmethoden iterativ eine nicht blockierende Operation ausführen, bis die erforderlichen Daten gelesen oder geschrieben wurden. In der Praxis kann eine Zeitüberschreitung erforderlich sein, um Serverausfälle zu beheben.Eine weitere Komplikation ist, dass der ESP32-Port Probleme hatte, die für einen fehlerfreien Betrieb ziemlich unangenehme Einbrüche erforderten. Ich habe nicht getestet, ob dies noch der Fall ist. Sock_nonblock.py-Modulveranschaulicht die erforderlichen Methoden. Dies ist keine funktionierende Demo und die Entscheidungen sind wahrscheinlich anwendungsabhängig.7.6.1 Probleme mit WiFiDer uasyncio- Streaming-Mechanismus ist nicht die beste Option zum Erkennen von WiFi-Ausfällen. Ich fand es notwendig, nicht blockierende Sockets zu verwenden, um einen ausfallsicheren Betrieb zu gewährleisten und den Client bei Fehlern erneut zu verbinden.In diesem Dokument werden die Probleme beschrieben, auf die ich in WiFi-Anwendungen gestoßen bin, die Sockets für längere Zeit offen halten, und die Lösung skizziert.Pltcmbietet einen robusten asynchronen MQTT-Client, der die Nachrichtenintegrität bei WLAN-Ausfällen gewährleistet. Eine einfache asynchrone serielle Vollduplex-Verbindung zwischen einem drahtlosen Client und einem verdrahteten Server mit garantierter Nachrichtenübermittlung wird beschrieben.7.7 Argumente des Konstruktors für EreignisschleifenEin kleiner Fehler kann auftreten, wenn Sie eine Ereignisschleife mit Werten erstellen müssen, die von den Standardwerten abweichen. Eine solche Schleife muss deklariert werden, bevor ein anderer Code mit asyncio ausgeführt wird, da diese Werte in diesem Code möglicherweise erforderlich sind. Andernfalls wird der Code mit den Standardwerten initialisiert: import uasyncio as asyncio import some_module bar = some_module.Bar()
Da beim Importieren eines Moduls Code ausgeführt werden kann, ist es am sichersten, eine Ereignisschleife unmittelbar nach dem Import von uasyncio zu instanziieren . import uasyncio as asyncio loop = asyncio.get_event_loop(runq_len=40, waitq_len=40) import some_module bar = some_module.Bar()
Beim Schreiben von Modulen zur Verwendung durch andere Programme ziehe ich es vor, uasyncio- Code beim Import nicht auszuführen . Schreiben Sie Funktionen und Methoden, um auf eine Ereignisschleife als Argument zu warten. Stellen Sie dann sicher, dass nur Anwendungen der obersten Ebene get_event_loop aufrufen : import uasyncio as asyncio import my_module
Dieses Problem wird hier diskutiert .8 Notizen für AnfängerDiese Notizen richten sich an Anfänger in asynchronem Code. Sie beginnen mit einer Beschreibung der Probleme, die Planer zu lösen versuchen, und geben einen Überblick über den Lösungsansatz von uasyncio .In Abschnitt 8.5 werden die relativen Vorteile der Module uasyncio und _ thread sowie die Gründe erläutert , warum Sie uasyncio- Coroutinen mit proaktiver Zeitplanung (_thread) möglicherweise bevorzugen .8.1 Problem 1: EreignisschleifenEine typische Firmware-Anwendung arbeitet ununterbrochen und sollte gleichzeitig auf externe Ereignisse reagieren, zu denen eine Spannungsänderung am ADC, das Auftreten eines Hardware-Interrupts oder ein im UART empfangenes Symbol oder auf Daten, die auf dem Sockel verfügbar sind, gehören können. Diese Ereignisse treten asynchron auf, und der Code sollte in der Lage sein, unabhängig von der Reihenfolge, in der sie auftreten, zu reagieren. Darüber hinaus können zeitabhängige Aufgaben erforderlich sein, z. B. das Blinken von LEDs.Die naheliegende Möglichkeit hierfür ist die uasycio- Ereignisschleife . Dieses Beispiel ist kein praktischer Code, sondern dient zur Veranschaulichung der allgemeinen Form der Ereignisschleife. def event_loop(): led_1_time = 0 led_1_period = 20 led_2_time = 0 led_2_period = 30 switch_state = switch.state()
Eine solche Schleife funktioniert für einfache Beispiele, aber mit zunehmender Anzahl von Ereignissen wird der Code schnell umständlich. Sie verletzen auch die Prinzipien der objektorientierten Programmierung, indem sie den größten Teil der Programmlogik an einem Ort kombinieren, anstatt Code mit einem gesteuerten Objekt zu verknüpfen. Wir wollen eine Klasse für eine blinkende LED entwickeln, die in ein Modul eingefügt und importiert werden kann. Der OOP-Ansatz für das Blinken von LEDs könnte folgendermaßen aussehen: import pyb class LED_flashable(): def __init__(self, led_no): self.led = pyb.LED(led_no) def flash(self, period): while True: self.led.toggle()
Mit dem Scheduler in uasyncio können Sie solche Klassen erstellen.8.2 Problem 2: BlockierungsmethodenAngenommen, Sie müssen eine bestimmte Anzahl von Bytes aus einem Socket lesen. Wenn Sie socket.read (n) standardmäßig mit einem blockierenden Socket aufrufen , wird es "blockieren" ( dh es kann nicht beendet werden), bis n Bytes empfangen werden . Während dieser Zeit reagiert die Anwendung nicht auf andere Ereignisse.Mit dem nicht blockierenden uasyncio- Socket können Sie eine asynchrone Lesemethode schreiben. Eine Aufgabe, für die Daten erforderlich sind, wird (notwendigerweise) blockiert, bis sie empfangen werden. In diesem Zeitraum werden jedoch andere Aufgaben ausgeführt, sodass die Anwendung weiterhin reagiert.8.3. Uasyncio-AnsätzeDie nächste Klasse verfügt über eine LED, die ein- und ausgeschaltet werden kann. Sie kann auch bei jeder Geschwindigkeit blinken. Die LED_async- Instanz verwendet die Ausführungsmethode , die für den kontinuierlichen Betrieb verwendet werden kann. Das Verhalten von LEDs kann mit den Methoden on (), off () und flash (secs) gesteuert werden . import pyb import uasyncio as asyncio class LED_async(): def __init__(self, led_no): self.led = pyb.LED(led_no) self.rate = 0 loop = asyncio.get_event_loop() loop.create_task(self.run()) async def run(self): while True: if self.rate <= 0: await asyncio.sleep_ms(200) else: self.led.toggle() await asyncio.sleep_ms(int(500 / self.rate)) def flash(self, rate): self.rate = rate def on(self): self.led.on() self.rate = 0 def off(self): self.led.off() self.rate = 0
Es ist zu beachten, dass on (), off () und flash () normale synchrone Methoden sind. Sie ändern das Verhalten der LED, kehren aber sofort zurück. Das Blinken erfolgt "im Hintergrund". Dies wird im nächsten Abschnitt ausführlich erläutert.Die Klasse entspricht dem OOP-Prinzip, bei dem die dem Gerät zugeordnete Logik in der Klasse gespeichert wird. Gleichzeitig stellt die Verwendung von uasyncio sicher, dass die Anwendung auf andere Ereignisse reagieren kann, während die LED blinkt. Das folgende Programm blinkt mit vier Pyboard- LEDs mit unterschiedlichen Frequenzen und reagiert auch auf die USR-Taste, die es vervollständigt. import pyb import uasyncio as asyncio from led_async import LED_async
Im Gegensatz zum ersten Beispiel einer Ereignisschleife befindet sich die dem Schalter zugeordnete Logik in einer Funktion, die von der Funktionalität der LED getrennt ist. Achten Sie auf den Code, der zum Starten des Schedulers verwendet wird: loop = asyncio.get_event_loop() loop.run_until_complete(killer())
8.4 Planung in uasyncioPython 3.5 und MicroPython unterstützen das Konzept einer asynchronen Funktion, die auch als Coroutine oder Task bezeichnet wird. Eine Coroutine muss mindestens eine wait- Anweisung enthalten . async def hello(): for _ in range(10): print('Hello world.') await asyncio.sleep(1)
Diese Funktion druckt zehnmal im Sekundentakt eine Nachricht. Während die Funktion in Erwartung einer Verzögerung angehalten wird , führt der Asyncio-Scheduler andere Aufgaben aus, wodurch die Illusion entsteht, sie gleichzeitig auszuführen.Wenn die Coroutine-Probleme auf asyncio.sleep_ms () oder asyncio.sleep () warten, wird die aktuelle Task angehalten und in eine Warteschlange gestellt, die nach Zeit geordnet ist, und die Ausführung wird mit der Task am Anfang der Warteschlange fortgesetzt. Die Warteschlange ist so konzipiert, dass selbst wenn der angegebene Ruhemodus Null ist, andere relevante Aufgaben ausgeführt werden, bis der Strom wieder aufgenommen wird. Dies ist eine „ehrliche Kreislaufplanung“. Es ist gängige Praxis, asyncio.sleep (0) -Schleifen abzuwarten .Damit die Task die Ausführung nicht verzögert. Die folgende Schleife wartet darauf, dass eine andere Task die globale Flag- Variable setzt . Leider monopolisiert es den Prozessor und verhindert den Start anderer Coroutinen: async def bad_code(): global flag while not flag: pass
Das Problem hierbei ist , dass keine andere Task gestartet wird, bis die Flagis-False- Schleife die Steuerung an den Scheduler übergibt . Der richtige Ansatz: async def good_code(): global flag while not flag: await asyncio.sleep(0)
Aus dem gleichen Grund ist es üblich, Verzögerungen festzulegen , z. B. utime.sleep (1), da andere Tasks für 1 s blockiert werden. Es ist richtiger, wait asyncio.sleep (1) zu verwenden .Beachten Sie, dass Verzögerungen, die von den Methoden uasyncio sleep und sleep_ms generiert werden, die angegebene Zeit überschreiten können. Dies liegt an der Tatsache, dass andere Aufgaben während der Verzögerung ausgeführt werden. Nach Ablauf der Verzögerungszeit wird die Ausführung erst fortgesetzt, wenn die ausgeführten Aufgaben warten oder beendet sind. Eine wohlerzogene Coroutine wird immer das Warten erklärenin regelmäßigen Abständen. Wenn eine genaue Verzögerung erforderlich ist, insbesondere wenn eine weniger als einige ms beträgt, muss möglicherweise utime.sleep_us (us) verwendet werden .8.5 Warum kollaboratives, nicht threadbasiertes Scheduling ( _thread )?Die anfängliche Reaktion von Anfängern auf die Idee, Koroutinen mitzuplanen, ist oft enttäuschend. Sicherlich ist Streaming-Planung besser? Warum sollte ich die Kontrolle explizit aufgeben, wenn die virtuelle Python-Maschine dies für mich tun kann?Bei eingebetteten Systemen bietet das Kollaborationsmodell zwei Vorteile.Das erste ist geringes Gewicht. Es ist möglich, dass eine große Anzahl von Coroutinen vorhanden ist, da suspendierte Coroutinen im Gegensatz zu geplanten Threads weniger Platz beanspruchen.Zweitens werden auf diese Weise einige der subtilen Probleme im Zusammenhang mit der Streaming-Planung vermieden.In der Praxis ist das kollaborative Multitasking weit verbreitet, insbesondere in Benutzeroberflächenanwendungen.Zur Verteidigung des Streaming-Planungsmodells zeige ich einen Vorteil: Wenn jemand schreibt for x in range ( 1000000 ):
Andere Aufgaben werden nicht blockiert. Das Kollaborationsmodell geht davon aus, dass die Schleife der Steuerung jeder Aufgabe explizit eine bestimmte Anzahl von Iterationen zuweisen soll , z. B. Code in eine Coroutine einfügen und in regelmäßigen Abständen den Befehl wait asyncio.sleep (0) ausgeben soll .Leider verblasst dieser Vorteil im Vergleich zu den Nachteilen. Einige davon sind in der Dokumentation zum Schreiben von Interrupt-Handlern beschrieben.. In einem Streaming-Planungsmodell kann jeder Thread jeden anderen Thread unterbrechen und die Daten ändern, die in anderen Threads verwendet werden können. In der Regel ist es viel einfacher, eine Sperre zu finden und zu beheben, die aufgrund eines Fehlers auftritt, der kein Ergebnis liefert, als manchmal sehr subtile und selten auftretende Fehler zu erkennen, die in Code auftreten können, der im Rahmen eines Modells mit Streaming-Planung geschrieben wurde.Einfach ausgedrückt, wenn Sie eine MicroPython- Coroutine schreiben , können Sie sicher sein, dass die Variablen nicht plötzlich von einer anderen Coroutine geändert werden: Ihre Coroutine hat die volle Kontrolle, bis sie wieder asyncio.sleep (0) erwartet .Denken Sie daran, dass Interrupt-Handler präventiv sind. Dies gilt sowohl für Hardware- als auch für Software-Interrupts, die an einer beliebigen Stelle in Ihrem Code auftreten können.Eine eloquente Diskussion zu Fragen der Streaming-Planung finden Sie hier .8.6 InteraktionIn nicht trivialen Anwendungen müssen Coroutinen interagieren. Es können herkömmliche Python- Methoden verwendet werden . Dazu gehören die Verwendung globaler Variablen oder die Deklaration von Coroutinen als Objektmethoden: Sie können Instanzvariablen gemeinsam nutzen. Alternativ kann ein veränderbares Objekt als Argument an eine Coroutine übergeben werden.Für das Streaming-Planungsmodell müssen Spezialisten sicherstellen, dass Klassen eine sichere Verbindung bereitstellen. In einem Kollaborationsmodell ist dies selten erforderlich.8.7. Poll ( Polling )Einige Hardware - Gerät wie ein Beschleunigungsmesser Pyboard , unterstützen keine Unterbrechungen und daher abgefragt werden soll ( das heißt periodisch überprüft). Polling kann auch in Verbindung mit Interrupt-Handlern verwendet werden: Der Interrupt-Handler verwaltet die Ausrüstung und setzt ein Flag. Die Coroutine fragt das Flag ab - wenn es gesetzt ist, werden Daten verarbeitet und das Flag zurückgesetzt. Der beste Ansatz ist die Verwendung der Event- Klasse .