Dart 2. Asynchrone Programmierung: Futures

Asynchrone Programmierung: Futures


Inhalt



Was ist wichtig:


  • Der Code in Dart wird in einer einzelnen Thread-Ausführung ( Note Thread - Thread ) ausgeführt.
  • Aufgrund von Code, der den Thread für eine lange Zeit benötigt (blockiert), kann das Programm einfrieren.
  • Future ( futures ) Objekte stellen die Ergebnisse asynchroner Operationen dar - Verarbeitung oder E / A, die später abgeschlossen werden.
  • Um die Ausführung in Zukunft bis zum Abschluss auszusetzen, verwenden Sie await in der asynchronen Funktion (oder then() wenn Sie die Future API verwenden).
  • Verwenden Sie zum Abfangen von Fehlern das try-catch Konstrukt (oder catchError() bei Verwendung der Future API) in der asynchronen Funktion.
  • Erstellen Sie für die gleichzeitige Verarbeitung ein Isolat (oder einen Worker für die Webanwendung).

Der Code in Dart wird in einem einzelnen Ausführungsthread ausgeführt. Wenn der Code mit langen Berechnungen beschäftigt ist oder auf eine E / A-Operation wartet, wird das gesamte Programm angehalten.


Durch asynchrone Vorgänge kann Ihr Programm andere Aufgaben ausführen, während Sie auf den Abschluss des Vorgangs warten. Dart verwendet futures , um die Ergebnisse asynchroner Operationen darzustellen. Sie können auch async and await oder die Future API verwenden, um mit futures zu arbeiten.


Eine Notiz


Der gesamte Code wird im Kontext des Isolats ausgeführt, dem der gesamte vom Code verwendete Speicher gehört. Es kann nicht mehr als eine Codeausführung im selben Isolat gestartet werden.

Für die parallele Ausführung von Codeblöcken können Sie diese in separate Isolate aufteilen. (Webanwendungen verwenden Worker anstelle von Isolaten.) In der Regel wird jedes der Isolate auf einem eigenen Prozessorkern ausgeführt. Isolate teilen sich nicht den Speicher und können nur interagieren, indem sie sich gegenseitig Nachrichten senden. Informationen zum Thema finden Sie in der Dokumentation zu Isolaten oder Arbeitern .

Einführung


Schauen wir uns ein Beispiel für Code an, der die Programmausführung „einfrieren“ kann:


 // Synchronous code void printDailyNewsDigest() { var newsDigest = gatherNewsReports(); // Can take a while. print(newsDigest); } main() { printDailyNewsDigest(); printWinningLotteryNumbers(); printWeatherForecast(); printBaseballScore(); } 

Unser Programm liest Nachrichten aus der Tagesdatei, zeigt sie an und zeigt dann Informationen an, die für den Benutzer immer noch von Interesse sind:


 <gathered news goes here> Winning lotto numbers: [23, 63, 87, 26, 2] Tomorrow's forecast: 70F, sunny. Baseball score: Red Sox 10, Yankees 0 

In diesem Beispiel besteht das Problem darin, dass alle Vorgänge nach dem Aufruf von gatherNewsReports() warten, bis gatherNewsReports() den Inhalt der Datei gatherNewsReports() , unabhängig davon, wie lange es dauert. Wenn das Lesen der Datei lange dauert, muss der Benutzer auf die Ergebnisse der Lotterie, die Wettervorhersage und den Gewinner eines kürzlich durchgeführten Spiels warten.


Um die Reaktionsfähigkeit der Anwendung aufrechtzuerhalten, verwenden Dart-Autoren ein asynchrones Modell, um Funktionen zu identifizieren, die möglicherweise teure Arbeiten ausführen. Solche Funktionen geben ihren Wert über futures .


Was ist die Zukunft?


future ist eine Instanz der Future <T> -Klasse, bei der es sich um eine asynchrone Operation handelt, die ein Ergebnis vom Typ T zurückgibt. Wenn das Ergebnis der Operation nicht verwendet wird, wird der Future<void> Typ durch Future<void> . Beim Aufrufen einer Funktion, die die future zurückgibt, passieren zwei Dinge:


  1. Die Funktion steht zur Ausführung in der Warteschlange und gibt ein unvollständiges Future Objekt zurück.
  2. Später, wenn der Vorgang abgeschlossen ist, wird die future mit einem Wert oder Fehler beendet.

Um future Code zu schreiben, haben Sie zwei Möglichkeiten:


  • Verwenden Sie async - await
  • Verwenden Sie die Future API

Async - warte


Die Schlüsselwörter async und await sind Teil der async Unterstützung von Dart. Mit ihnen können Sie asynchronen Code schreiben, der wie synchroner Code aussieht und die Future API nicht verwendet. Eine asynchrone Funktion ist eine Funktion mit dem Schlüsselwort async vor ihrem Hauptteil. Das Schlüsselwort await funktioniert nur in asynchronen Funktionen.


Hinweis: In Dart 1.x verzögern asynchrone Funktionen sofort die Ausführung. In Dart 2 werden asynchrone Funktionen nicht sofort angehalten, sondern synchron ausgeführt, bis das erste await oder return .

Der folgende Code simuliert das Lesen von Nachrichten aus einer Datei mit async - await . Öffnen Sie DartPad mit der Anwendung , starten Sie und klicken Sie auf KONSOLE, um das Ergebnis anzuzeigen.


Beispielcode
 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import 'dart:async'; Future<void> printDailyNewsDigest() async { var newsDigest = await gatherNewsReports(); print(newsDigest); } main() { printDailyNewsDigest(); printWinningLotteryNumbers(); printWeatherForecast(); printBaseballScore(); } printWinningLotteryNumbers() { print('Winning lotto numbers: [23, 63, 87, 26, 2]'); } printWeatherForecast() { print("Tomorrow's forecast: 70F, sunny."); } printBaseballScore() { print('Baseball score: Red Sox 10, Yankees 0'); } const news = '<gathered news goes here>'; const oneSecond = Duration(seconds: 1); // Imagine that this function is more complex and slow. :) Future<String> gatherNewsReports() => Future.delayed(oneSecond, () => news); // Alternatively, you can get news from a server using features // from either dart:io or dart:html. For example: // // import 'dart:html'; // // Future<String> gatherNewsReportsFromServer() => HttpRequest.getString( // 'https://www.dartlang.org/f/dailyNewsDigest.txt', // ); 

Beachten Sie, dass wir zuerst printDailyNewsDigest() aufrufen, die Nachrichten jedoch zuletzt gedruckt werden, selbst wenn die Datei nur eine Zeile enthält. Dies liegt daran, dass der Code, der die Datei liest und druckt, asynchron ausgeführt wird.


In diesem Beispiel printDailyNewsDigest() gatherNewsReports() , das nicht blockiert. Durch Aufrufen der Methode gatherNewsReports() wird gatherNewsReports() Job in die gatherNewsReports() Ausführung des restlichen Codes wird jedoch nicht gestoppt. Das Programm zeigt die Lotterienummern, Prognosen und Ergebnisse eines Baseballspiels an. Das Programm druckt die Nachrichten, nachdem die Sammlung von gatherNewsReports() . Wenn gatherNewsReports() einige Zeit benötigt, um seine Arbeit abzuschließen, passiert nichts Schlimmes: Der Benutzer kann andere Dinge lesen, bevor der tägliche News Digest gedruckt wird.


Achten Sie auf Rückgabetypen. Der Rückgabetyp der Funktion gatherNewsReports() ist Future<String> gatherNewsReports() bedeutet, dass eine future , die mit einem Zeichenfolgenwert endet. Die Funktion printDailyNewsDigest() , die keinen Wert printDailyNewsDigest() , hat den Rückgabetyp Future<void> .


Das folgende Diagramm zeigt die Schritte zur Codeausführung.



  1. Die Anwendung wird gestartet.
  2. Die main() Funktion printDailyNewsDigest() asynchrone Funktion printDailyNewsDigest() und wird synchron ausgeführt.
  3. printDailyNewsDigest() verwendet await , um die Funktion gatherNewsReports() aufzurufen, die gatherNewsReports() wird.
  4. gatherNewsReports() gibt eine unvollendete future (eine Instanz von Future<String> ).
  5. Da printDailyNewsDigest() eine asynchrone Funktion ist und einen Wert erwartet, wird die Ausführung printDailyNewsDigest() und die unvollständige future (in diesem Fall Future<void> ) an die aufrufende Funktion main () .
  6. Die restlichen Ausgabefunktionen werden ausgeführt. Da sie synchron sind, wird jede Funktion vollständig ausgeführt, bevor zur nächsten übergegangen wird. Beispielsweise werden alle Lottozahlen vor der Wettervorhersage angezeigt.
  7. Nach Abschluss von main() asynchrone Funktionen die Ausführung fortsetzen. Zuerst erhalten wir die future mit Neuigkeiten über die Fertigstellung von gatherNewsReports() . Anschließend setzt printDailyNewsDigest() die Ausführung fort und zeigt die Nachrichten an.
  8. Am Ende der Ausführung von printDailyNewsDigest() ist die ursprünglich empfangene future und die Anwendung wird beendet.

Beachten Sie, dass die asynchrone Funktion sofort (synchron) gestartet wird. Die Funktion unterbricht die Ausführung und gibt eine unvollendete future wenn das erste Auftreten eines der folgenden Ereignisse eintritt:


  • Der erste await Ausdruck (nachdem die Funktion aus diesem Ausdruck die unvollständige future ).
  • Jede return in einer Funktion.
  • Das Ende des Funktionskörpers.

Fehlerbehandlung


Höchstwahrscheinlich möchten Sie einen Fehler bei der Ausführung der Funktion "abfangen", die die future zurückgibt. In asynchronen Funktionen können Sie Fehler mit try-catch :


 Future<void> printDailyNewsDigest() async { try { var newsDigest = await gatherNewsReports(); print(newsDigest); } catch (e) { // Handle error... } } 

Ein try-catch mit asynchronem Code verhält sich genauso wie mit synchronem Code: Wenn der Code im try Block eine Ausnahme try , wird der Code in catch ausgeführt.


Sequentielle Ausführung


Sie können mehrere await , um sicherzustellen, dass jede Anweisung abgeschlossen ist, bevor Sie Folgendes ausführen:


 // Sequential processing using async and await. main() async { await expensiveA(); await expensiveB(); doSomethingWith(await expensiveC()); } 

expensiveB() Funktion expensiveB() wird erst ausgeführt, wenn die Funktion expensiveA() abgeschlossen ist, und so weiter.


Zukünftige API


Bevor async und await in Dart 1.9 hinzugefügt wurden, mussten Sie die Future API verwenden. Sie können die Verwendung der Future API immer noch in altem Code und in Code sehen, der mehr Funktionen benötigt als async–await zu bieten hat.


Verwenden Sie die then() -Methode, um asynchronen Code mithilfe der Future API zu schreiben und den Rückruf zu registrieren. Dieser Rückruf funktioniert, wenn die future abgeschlossen ist.


Der folgende Code simuliert das Lesen von Nachrichten aus einer Datei mithilfe der Future API. Öffnen Sie DartPad mit der Anwendung , starten Sie und klicken Sie auf KONSOLE, um das Ergebnis anzuzeigen.


Beispielcode
 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import 'dart:async'; Future<void> printDailyNewsDigest() { final future = gatherNewsReports(); return future.then(print); // You don't *have* to return the future here. // But if you don't, callers can't await it. } main() { printDailyNewsDigest(); printWinningLotteryNumbers(); printWeatherForecast(); printBaseballScore(); } printWinningLotteryNumbers() { print('Winning lotto numbers: [23, 63, 87, 26, 2]'); } printWeatherForecast() { print("Tomorrow's forecast: 70F, sunny."); } printBaseballScore() { print('Baseball score: Red Sox 10, Yankees 0'); } const news = '<gathered news goes here>'; const oneSecond = Duration(seconds: 1); // Imagine that this function is more complex and slow. :) Future<String> gatherNewsReports() => Future.delayed(oneSecond, () => news); // Alternatively, you can get news from a server using features // from either dart:io or dart:html. For example: // // import 'dart:html'; // // Future<String> gatherNewsReportsFromServer() => HttpRequest.getString( // 'https://www.dartlang.org/f/dailyNewsDigest.txt', // ); 

Beachten Sie, dass wir zuerst printDailyNewsDigest() aufrufen, die Nachrichten jedoch zuletzt gedruckt werden, selbst wenn die Datei nur eine Zeile enthält. Dies liegt daran, dass der Code, der die Datei liest und druckt, asynchron ausgeführt wird.


Diese Anwendung läuft wie folgt ab:


  1. Die Anwendung wird gestartet.
  2. Die Hauptfunktion ruft printDailyNewsDigest() , das das Ergebnis nicht sofort gatherNewsReports() , sondern zuerst gatherNewsReports() .
  3. gatherNewsReports() beginnt mit dem Lesen von Nachrichten und gibt die future .
  4. printDailyNewsDigest() verwendet then() , um einen Rückruf zu registrieren, der den am Ende der future erhaltenen Wert als Parameter verwendet. Der Aufruf then() gibt eine neue future , die mit dem Wert endet, den der Rückruf von then() zurückgibt.
  5. Die restlichen Ausgabefunktionen werden ausgeführt. Da sie synchron sind, wird jede Funktion vollständig ausgeführt, bevor zur nächsten übergegangen wird. Beispielsweise werden alle Lottozahlen vor der Wettervorhersage angezeigt.
  6. Wenn alle Nachrichten empfangen wurden, endet die von der Funktion gatherNewsReports() future mit einer Zeichenfolge, die die gesammelten Nachrichten enthält.
  7. Der in then() in printDailyNewsDigest() angegebene Code wird ausgeführt, printDailyNewsDigest() Nachrichten zu printDailyNewsDigest() .
  8. Die Anwendung wird heruntergefahren.

Hinweis: In der Funktion printDailyNewsDigest() entspricht der Code future.then(print) dem folgenden: future.then((newsDigest) => print(newsDigest)) .

Außerdem kann der Code in then() geschweifte Klammern verwenden:


 Future<void> printDailyNewsDigest() { final future = gatherNewsReports(); return future.then((newsDigest) { print(newsDigest); // Do something else... }); } 

Sie müssen das Rückrufargument in then() angeben, auch wenn future vom Typ Future<void> . Konventionell wird ein nicht verwendetes Argument durch _ (Unterstrich) definiert.


 final future = printDailyNewsDigest(); return future.then((_) { // Code that doesn't use the `_` parameter... print('All reports printed.'); }); 

Fehlerbehandlung


Mit der Future API können Sie den Fehler mit catchError() :


 Future<void> printDailyNewsDigest() => gatherNewsReports().then(print).catchError(handleError); 

Wenn die Nachrichten nicht lesbar sind, wird der obige Code wie folgt ausgeführt:


  1. future von gatherNewsReports() future schlägt fehl.
  2. future von then() future schlägt fehl, print() nicht aufgerufen.
  3. Der catchError() in catchError() ( handleError() ) fängt den Fehler ab, die von catchError() future wird catchError() abgeschlossen und der Fehler breitet sich nicht weiter aus.

Die then() -Kette - catchError() ist ein gängiges Muster bei Verwendung der Future API. Betrachten Sie dieses Paar als das Äquivalent eines try-catch in der Future API.

Wie then () gibt catchError () eine neue future , die mit dem Rückgabewert des Rückrufs endet. Um in das Thema einzutauchen , lesen Sie Futures und Fehlerbehandlung .


Aufruf mehrerer Funktionen, die die future


Betrachten wir drei Funktionen: expensiveA() , expensiveB() , expensiveC() , die die future . Sie können sie nacheinander aufrufen (eine Funktion startet nach Abschluss der vorherigen) oder Sie können sie alle gleichzeitig ausführen und etwas tun, sobald alle Werte zurückgegeben werden. Die Benutzeroberfläche von Future ist flexibel genug, um beide Anwendungsfälle zu implementieren.


Eine Funktionskette ruft mit then()
Wenn die Funktionen, die die future zurückgeben, in der richtigen Reihenfolge ausgeführt werden müssen, verwenden Sie die Kette von then() :


 expensiveA() .then((aValue) => expensiveB()) .then((bValue) => expensiveC()) .then((cValue) => doSomethingWith(cValue)); 

Das Anhängen von Rückrufen funktioniert ebenfalls, ist jedoch schwieriger zu lesen. ( Hinweis http://callbackhell.com/ )


Warten auf den Abschluss mehrerer futures mit Future.wait()
Wenn die Ausführungsreihenfolge der Funktionen nicht wichtig ist, können Sie Future.wait() . Wenn Sie die futures Liste für Parameter für die Future.wait () -Funktion angeben, wird sofort die future . Diese future endet erst, wenn alle angegebenen futures . Diese future endet mit einer Liste der Ergebnisse aller angegebenen futures .


 Future.wait([expensiveA(), expensiveB(), expensiveC()]) .then((List responses) => chooseBestResponse(responses, moreInfo)) .catchError(handleError); 

Wenn ein Aufruf einer der Funktionen fehlschlägt, schlägt auch die von Future.wait() future fehl. Verwenden Sie catchError() , um diesen Fehler catchError() .




Was noch zu lesen?


Dart 2. Asynchrone Programmierung: Datenströme

Source: https://habr.com/ru/post/de442282/


All Articles