Tutorial 8 – Damit alles glatt läuft¶
Bisher war unsere Anwendung relativ einfach – sie zeigte GUI-Widgets an, rief eine einfache Bibliothek eines Drittanbieters auf und zeigte die Ausgabe in einem Dialogfeld an. All diese Vorgänge laufen sehr schnell ab, und unsere Anwendung bleibt reaktionsschnell.
In einer realen Anwendung müssen wir jedoch komplexe Aufgaben oder Berechnungen ausführen, deren Ausführung einige Zeit in Anspruch nehmen kann – und während diese Aufgaben ausgeführt werden, soll unsere Anwendung weiterhin reaktionsfähig bleiben. Nehmen wir eine Änderung an unserer Anwendung vor, deren Ausführung etwas Zeit in Anspruch nehmen könnte, und sehen wir uns an, welche Änderungen erforderlich sind, um dieses Verhalten zu berücksichtigen.
Zugriff auf API¶
Eine häufige zeitaufwändige Aufgabe, die eine App ausführen muss, besteht darin, eine Anfrage an eine Web-API zu stellen, um Daten abzurufen und diese Daten dem Benutzer anzuzeigen. Web-APIs benötigen manchmal ein oder zwei Sekunden, um zu antworten. Wenn wir also eine solche API aufrufen, müssen wir sicherstellen, dass unsere Anwendung nicht unempfänglich wird, während wir auf die Antwort der Web-API warten.
Dies ist eine Spielzeug-App, daher haben wir keine echte API, mit der wir
arbeiten können. Wir verwenden daher einen Beispiel-API-Endpunkt als
Datenquelle. Wenn Sie
https://tutorial.beeware.org/tutorial/message.json
in Ihrem Browser öffnen, erhalten Sie eine JSON-Nutzlast mit einer Nachricht.
Die Python-Standardbibliothek enthält alle Tools, die Sie für den Zugriff auf eine API benötigen. Die integrierten APIs sind jedoch sehr niedrigschwellig. Sie sind gute Implementierungen des HTTP-Protokolls, erfordern jedoch vom Benutzer die Verwaltung vieler niedrigschwelliger Details wie URL-Umleitung, Sitzungen, Authentifizierung und Payload-Kodierung. Als „normaler Browser-Benutzer” sind Sie wahrscheinlich daran gewöhnt, diese Details als selbstverständlich hinzunehmen, da der Browser sie für Sie verwaltet.
Infolgedessen wurden Bibliotheken von Drittanbietern entwickelt, die die
eingebauten APIs umhüllen und eine einfachere API bereitstellen, die der
alltäglichen Browsererfahrung besser entspricht. Wir werden eine dieser
Bibliotheken verwenden, um auf die {JSON} Platzhalter-API - eine Bibliothek
namens httpx. Briefcase verwendet httpx
intern, so dass sie sich bereits in Ihrer lokalen Umgebung befindet - Sie müssen
sie nicht separat installieren, um sie hier zu verwenden.
Fügen wir unserer App einen httpx-API-Aufruf hinzu. Ändern Sie die Einstellung
requires in unserer pyproject.toml, um die neue Anforderung aufzunehmen:
requires = [
"faker",
"httpx",
]
Fügen Sie am Anfang der Datei app.py einen Import hinzu, um httpx zu
importieren:
import httpx
Dann ändern Sie den Callback say_hello() so, dass er wie folgt aussieht:
async def say_hello(self, widget):
fake = faker.Faker()
with httpx.Client() as client:
response = client.get("https://tutorial.beeware.org/tutorial/message.json")
payload = response.json()
await self.main_window.dialog(
toga.InfoDialog(
greeting(self.name_input.value),
f"A message from {fake.name()}: {payload['body']}",
)
)
Dadurch wird der Callback say_hello() so geändert, dass er bei Aufruf
Folgendes ausführt:
- Führen Sie eine GET-Anfrage an die JSON-Platzhalter-API durch, um Beitrag 42 abzurufen;
- Die Antwort als JSON dekodieren;
- den Text des Beitrags extrahieren; und
- Fügen Sie den Text dieses Beitrags als Text des „Nachrichten”-Dialogfelds ein, anstelle des von Faker generierten Textes.
Lassen Sie uns unsere aktualisierte App im Briefcase-Entwicklermodus ausführen,
um zu überprüfen, ob unsere Änderung funktioniert hat. Da wir eine neue
Anforderung hinzugefügt haben, müssen wir den Entwicklermodus anweisen, die
Anforderungen neu zu installieren, indem wir das Argument -r verwenden:
(beeware-venv) $ briefcase dev -r
[helloworld] Installing requirements...
...
[helloworld] Starting in dev mode...
===========================================================================
Wenn Sie einen Namen eingeben und auf die Schaltfläche klicken, sollte ein Dialogfeld angezeigt werden, das in etwa wie folgt aussieht:

(beeware-venv) $ briefcase dev -r
[helloworld] Installing requirements...
...
[helloworld] Starting in dev mode...
===========================================================================
Wenn Sie einen Namen eingeben und auf die Schaltfläche klicken, sollte ein Dialogfeld angezeigt werden, das in etwa wie folgt aussieht:

(beeware-venv) C:\...>briefcase dev -r
[helloworld] Installing requirements...
...
[helloworld] Starting in dev mode...
===========================================================================
Wenn Sie einen Namen eingeben und auf die Schaltfläche klicken, sollte ein Dialogfeld angezeigt werden, das in etwa wie folgt aussieht:

Sie können eine Android-App nicht im Entwicklermodus ausführen – befolgen Sie die Anweisungen für die von Ihnen gewählte Desktop-Plattform.
Sie können eine iOS-App nicht im Entwicklermodus ausführen – befolgen Sie die Anweisungen für die von Ihnen gewählte Desktop-Plattform.
Wenn Sie keine wirklich schnelle Internetverbindung haben, werden Sie möglicherweise feststellen, dass die Benutzeroberfläche Ihrer App für einen Moment einfriert, wenn Sie die Taste drücken. Das Betriebssystem zeigt dies möglicherweise sogar mit einem „Beachball”- oder „Spinner”-Cursor an, um zu signalisieren, dass die App nicht reagiert.
Wenn Sie keine wirklich schnelle Internetverbindung haben, werden Sie vielleicht feststellen, dass die grafische Benutzeroberfläche Ihrer Anwendung beim Drücken der Schaltfläche kurzzeitig blockiert wird. Das liegt daran, dass die Webanforderung, die wir gestellt haben, synchron ist. Wenn unsere Anwendung die Webanforderung stellt, wartet sie, bis die API eine Antwort zurückgibt, bevor sie fortfährt. Während sie wartet, erlaubt sie der Anwendung nicht, neu zu zeichnen - und das Ergebnis ist, dass die Anwendung blockiert.
GUI-Ereignis-Schleifen¶
Um zu verstehen, warum das so ist, müssen wir uns mit den Details der Funktionsweise einer GUI-Anwendung befassen. Die Einzelheiten variieren je nach Plattform, aber die grundlegenden Konzepte sind dieselben, unabhängig von der Plattform oder der GUI-Umgebung, die Sie verwenden.
Eine GUI-Anwendung besteht im Grunde aus einer einzigen Schleife, die etwa so aussieht:
while not app.quit_requested():
app.process_events()
app.redraw()
Diese Schleife wird Ereignisschleife genannt. (Dies sind keine tatsächlichen Methodennamen - es ist eine Veranschaulichung dessen, was im "Pseudocode" vor sich geht).
Wenn Sie auf eine Schaltfläche klicken, eine Bildlaufleiste ziehen oder eine
Taste drücken, erzeugen Sie ein "Ereignis". Dieses "Ereignis" wird in eine
Warteschlange gestellt, und die Anwendung wird die Warteschlange von Ereignissen
verarbeiten, wenn sie das nächste Mal die Gelegenheit dazu hat. Der
Benutzercode, der als Reaktion auf das Ereignis ausgelöst wird, wird
Ereignishandler genannt. Diese Event-Handler werden als Teil des
process_events() Aufrufs aufgerufen.
Sobald eine Anwendung alle verfügbaren Ereignisse verarbeitet hat, wird sie die
grafische Benutzeroberfläche neu zeichnen(). Dabei werden alle Änderungen
berücksichtigt, die Ereignisse an der Anzeige der Anwendung verursacht haben,
sowie alles andere, was im Betriebssystem vor sich geht - zum Beispiel können
die Fenster einer anderen Anwendung einen Teil des Fensters unserer Anwendung
verdecken oder sichtbar machen, und die Neuzeichnung unserer Anwendung muss den
Teil des Fensters widerspiegeln, der gerade sichtbar ist.
Ein wichtiges Detail: Während eine Anwendung ein Ereignis verarbeitet, kann sie nicht neu zeichnen und kann sie keine anderen Ereignisse verarbeiten.
Das bedeutet, dass jede in einem Event-Handler enthaltene Benutzerlogik schnell abgeschlossen werden muss. Jede Verzögerung bei der Fertigstellung des Event-Handlers wird vom Benutzer als Verlangsamung (oder Stopp) der GUI-Aktualisierungen wahrgenommen. Wenn diese Verzögerung lang genug ist, kann Ihr Betriebssystem dies als Problem melden - die macOS-"Beachball"- und Windows-"Spinner"-Symbole sind das Betriebssystem, das Ihnen mitteilt, dass Ihre Anwendung in einem Event-Handler zu lange braucht.
Einfache Operationen wie "Aktualisieren eines Etiketts" oder "Neuberechnung der Gesamtsumme der Eingaben" sind leicht und schnell zu erledigen. Es gibt jedoch eine Reihe von Vorgängen, die nicht schnell erledigt werden können. Wenn Sie eine komplexe mathematische Berechnung durchführen, alle Dateien in einem Dateisystem indizieren oder eine große Netzwerkanforderung ausführen, können Sie das nicht "einfach schnell erledigen" - die Vorgänge sind von Natur aus langsam.
Wie können wir also langlebige Operationen in einer GUI-Anwendung durchführen?
Asynchrone Programmierung¶
Was wir brauchen, ist eine Möglichkeit, einer Anwendung in der Mitte eines langlebigen Event-Handlers mitzuteilen, dass es in Ordnung ist, die Kontrolle vorübergehend an die Event-Schleife zurückzugeben, solange wir dort fortfahren können, wo wir aufgehört haben. Es liegt an der Anwendung, zu bestimmen, wann diese Freigabe erfolgen kann; aber wenn die Anwendung die Kontrolle regelmäßig an die Ereignisschleife freigibt, können wir einen lang laufenden Event-Handler haben und eine reaktionsfähige Benutzeroberfläche beibehalten.
Wir können dies mit Hilfe der asynchronen Programmierung erreichen. Asynchrone Programmierung ist eine Art, ein Programm zu beschreiben, das es dem Interpreter erlaubt, mehrere Funktionen gleichzeitig auszuführen und die Ressourcen zwischen allen gleichzeitig laufenden Funktionen zu teilen.
Asynchrone Funktionen (so genannte Co-Routinen) müssen ausdrücklich als asynchron deklariert werden. Sie müssen auch intern erklären, wann die Möglichkeit besteht, den Kontext zu einer anderen Co-Routine zu wechseln.
In Python wird die asynchrone Programmierung mit den Schlüsselwörtern async
und await, und dem Modul
asyncio in der
Standardbibliothek implementiert. Mit dem Schlüsselwort async können wir eine
Funktion als asynchrone Co-Routine deklarieren. Mit dem Schlüsselwort await
kann man angeben, wann die Möglichkeit besteht, den Kontext zu einer anderen
Co-Routine zu wechseln. Das Modul
asyncio bietet einige andere
nützliche Werkzeuge und Primitive für asynchrone Programmierung.
Asynchronität des Tutorials¶
Um unser Tutorial asynchron zu machen, ändern Sie den "say_hello()"-Ereignishandler so, dass er wie folgt aussieht:
async def say_hello(self, widget):
fake = faker.Faker()
async with httpx.AsyncClient() as client:
response = await client.get("https://jsonplaceholder.typicode.com/posts/42")
payload = response.json()
await self.main_window.dialog(
toga.InfoDialog(
greeting(self.name_input.value),
f"A message from {fake.name()}: {payload['body']}",
)
)
In diesem Code gibt es nur 4 Änderungen gegenüber der vorherigen Version:
- Der Client, der erzeugt wird, ist ein asynchroner
AsyncClient(), anstatt eines synchronenClient(). Dies teilthttpxmit, dass es im asynchronen Modus und nicht im synchronen Modus arbeiten soll. - Der zur Erstellung des Clients verwendete Kontextmanager ist als
asyncmarkiert. Dies sagt Python, dass es eine Möglichkeit gibt, die Kontrolle abzugeben, wenn der Kontextmanager betreten und verlassen wird. - Der Aufruf von
geterfolgt mit dem Schlüsselwortawait. Damit wird der App mitgeteilt, dass sie während des Wartens auf die Antwort vom Netzwerk die Kontrolle an die Ereignisschleife abgeben kann. Dieses Schlüsselwort haben wir bereits gesehen – wir verwendenawaitauch beim Anzeigen des Dialogfelds. Der Grund für diese Verwendung ist derselbe wie bei der HTTP-Anfrage: Wir müssen der App mitteilen, dass es in Ordnung ist, die Kontrolle an die Ereignisschleife zurückzugeben, während das Dialogfeld angezeigt wird und wir darauf warten, dass der Benutzer eine Schaltfläche drückt.
Es ist auch wichtig zu beachten, dass der Handler selbst als async def
definiert ist und nicht nur als def. Dadurch wird Python mitgeteilt, dass es
sich bei der Methode um eine asynchrone Coroutine handelt. Diese Änderung haben
wir bereits in Tutorial 3 vorgenommen, als wir das Dialogfeld hinzugefügt haben.
Sie können await-Anweisungen nur innerhalb einer Methode verwenden, die als
async def deklariert ist.
Toga erlaubt es Ihnen, reguläre Methoden oder asynchrone Co-Routinen als Handler zu verwenden; Toga verwaltet alles hinter den Kulissen, um sicherzustellen, dass der Handler wie gewünscht aufgerufen oder erwartet wird.
Wenn Sie diese Änderungen speichern und die Anwendung erneut ausführen (entweder
mit briefcase dev im Entwicklungsmodus oder indem Sie die gepackte Anwendung
aktualisieren und erneut ausführen), wird es keine offensichtlichen Änderungen
an der Anwendung geben. Wenn Sie jedoch auf die Schaltfläche klicken, um den
Dialog auszulösen, können Sie eine Reihe von subtilen Verbesserungen
feststellen:
- Die Schaltfläche kehrt in einen "nicht angeklickten" Zustand zurück, anstatt in einem "angeklickten" Zustand zu verharren.
- Das "Beachball"/"Spinner"-Symbol wird nicht angezeigt.
- Wenn Sie das Anwendungsfenster verschieben/vergrößern, während Sie darauf warten, dass der Dialog erscheint, wird das Fenster neu gezeichnet.
- Wenn Sie versuchen, ein Anwendungsmenü zu öffnen, wird das Menü sofort angezeigt.
Wir können nun die vollständige App ausführen. Da wir jedoch eine zusätzliche
Anforderung (httpx) hinzugefügt haben, müssen wir auch die Anforderungen
unserer App aktualisieren. Dies können wir tun, indem wir -r an briefcase
run übergeben. Dadurch werden die Anforderungen unserer App aktualisiert, die
App neu erstellt und anschließend gestartet:
(beeware-venv) $ briefcase run -r
(beeware-venv) $ briefcase run -r
(beeware-venv) C:\...>briefcase run -r
(beeware-venv) $ briefcase run android -r
(beeware-venv) $ briefcase run iOS -r
Sie sollten sehen, dass Ihre App ausgeführt wird und weiterhin reagiert, wenn Sie die Schaltfläche drücken und Netzwerk-Inhalte abgerufen werden.
Nächste Schritte¶
Dies war ein kleiner Vorgeschmack darauf, was Sie mit den Tools des BeeWare-Projekts alles machen können. Im Laufe dieses Tutorials haben Sie:
- Ein neues GUI-App-Projekt erstellt;
- Führen Sie diese App im Entwicklungsmodus aus;
- Die App als eigenständige Binärdatei für ein Desktop-Betriebssystem erstellt;
- Dieses Projekt für die Weitergabe an andere vorbereitet;
- Führen Sie die App auf einem mobilen Simulator und/oder Gerät aus;
- Führen Sie die App als Web-App aus;
- Eine Abhängigkeit von Drittanbietern zu Ihrer App hinzugefügt; und
- Die App wurde so geändert, dass sie weiterhin reaktionsschnell bleibt.
Also – wie geht es nun weiter?
- Wenn Sie noch weiter gehen möchten, gibt es einige zusätzliche Themen-Tutorials, die auf spezifische Aspekte der Anwendungsentwicklung eingehen.
- Wenn Sie mehr darüber wissen möchten, wie man mit Toga komplexe Benutzeroberflächen erstellt, können Sie sich in Togas Dokumentation vertiefen. Toga hat auch ein eigenes Tutorial zur Demonstration der Verwendung verschiedener Funktionen des Widget-Toolkits.
- Wenn Sie mehr über die Möglichkeiten von Briefcase erfahren möchten, können Sie die Briefcase-Dokumentation lesen.