Dart asynchrone Programmierung

Asynchrone Programmierung ist eine der grundlegendsten Fähigkeiten in der modernen Anwendungsentwicklung.

Ob Netzwerkanfragen, Dateilesen und -schreiben oder Datenbankoperationen – asynchrone Verarbeitung ist erforderlich, um den Hauptthread nicht zu blockieren.

Dieses Kapitel führt in das Konzept von Future, die async/await-Syntax und den Umgang mit asynchronen Fehlern ein.


Warum asynchrone Programmierung benötigt wird

Verstehen Sie zunächst den Unterschied zwischen synchron und asynchron.

Synchroner Code wird Zeile für Zeile in der Reihenfolge ausgeführt; die nächste Zeile beginnt erst, wenn die vorherige Zeile beendet ist.

Wenn eine Operation warten muss (z. B. eine Netzwerkanfrage), bleibt das gesamte Programm hängen.

Beispiel

Vergleiche die Ausführungseffekte von synchronem und asynchronem Code:

// Simuliere eine zeitaufwändige Operation
String fetchDataSync() {
  // Synchroner Code: Angenommen, wir müssen hier 2 Sekunden warten
  // Während dieser 2 Sekunden kann das gesamte Programm nichts tun
  sleep(Duration(seconds: 2));  // Blockierendes Warten
  return 'Data fetching complete';
}

// Asynchrone Version: blockiert den Hauptthread nicht
Future<String> fetchDataAsync() async {
  // Asynchrones Warten: Während dieser 2 Sekunden kann das Programm weiterhin andere Aufgaben ausführen
  await Future.delayed(Duration(seconds: 2));
  return 'Data fetching complete';
}

void main() async {
  print('Beginne mit dem synchronen Abrufen von Daten...');
  var syncResult = fetchDataSync();
  print('Synchrones Ergebnis: $syncResult');
  print('(Hinweis: Das Programm hängt während der synchronen Ausführung fest)');

  print('\n'Beginne mit dem asynchronen Abrufen von Daten...');
  print('Nach dem Initiieren der Anfrage führt das Programm weiterhin andere Aufgaben aus...');
  var asyncResult = await fetchDataAsync();
  print('Asynchrones Ergebnis: $asyncResult');
  print('(Hinweis: Das Programm hängt während der asynchronen Ausführung nicht fest)');
}
Starting to fetch data synchronously...
Synchronous result: Data fetch complete
(Note: The program is stuck during synchronous execution)

Starting to fetch data asynchronously...
After sending the request, the program continues executing other tasks...
Asynchronous result: Data fetch complete
(The program is not stuck during asynchronous execution)

In Dart sollten alle I/O-Operationen (Dateien, Netzwerk, Datenbank) asynchrone Methoden verwenden. Synchrones I/O blockiert das gesamte Isolate, was in Flutter-Anwendungen zu UI-Ruckeln führt.


Das Konzept von Future

FutureStellt einen Wert dar, der „irgendwann in der Zukunft abgeschlossen wird“.

Es ist wie ein Versprechen: Ich habe jetzt noch kein Ergebnis, aber ich werde dir in der Zukunft definitiv einen Wert vom Typ T (oder einen Fehler) geben.

Future hat drei Zustände:

StateBeschreibung
UnabgeschlossenDer asynchrone Vorgang läuft noch
Mit Daten abgeschlossenDer Vorgang war erfolgreich und das Future enthält den Ergebniswert
Abgeschlossen, aber mit Fehlern (Abgeschlossen mit Fehler)Der Vorgang schlägt fehl und das Future enthält die Fehlerinformation.

Beispiel

Ein Future erstellen und verwenden:

void main() {
  print('Program start');

  // Ein Future erstellen
  Future<String> future = Future(() {
    // Diese Funktion wird irgendwann in der Zukunft ausgeführt
    return 'BEISPIEL asynchrones Ergebnis';
  });

  print('Future erstellt, aber noch nicht abgeschlossen');

  // Callback registrieren: ausführen, wenn der Future abgeschlossen ist
  future.then((result) {
    print('Erhaltenes Ergebnis: $result');
  });

  print('Programm läuft weiter...');
}
程序开始
Future 已创建,但还没完成
程序继续执行...
收到结果: EXAMPLE 异步结果

Beachte die Reihenfolge der Ausgabe: "Programm wird weiter ausgeführt" wird zuerst ausgegeben, und "Ergebnis erhalten" wird danach ausgegeben.

Dies zeigt, dass Future-Callbacks asynchron ausgeführt werden und den nachfolgenden Code nicht blockieren.


async / await-Syntax

async und await sind die Kernsyntax für den Umgang mit asynchronen Operationen in Dart.

async kennzeichnet eine Funktion als asynchron, und await wartet darauf, dass ein Future abgeschlossen wird, und ruft dessen Ergebnis ab.

Beispiel

// Das Schlüsselwort async kennzeichnet eine Funktion als asynchrone Funktion
// Der Rückgabetyp einer async-Funktion muss Future<T> sein
Future<String> fetchUserName(int id) async {
  // await wartet darauf, dass das Future abgeschlossen wird, und ruft seinen Wert ab
  // Während des Wartens pausiert die aktuelle Funktion die Ausführung, blockiert aber keinen anderen Code
  await Future.delayed(Duration(seconds: 1));
  return 'User$id';
}

Future<int> fetchUserAge(int id) async {
  await Future.delayed(Duration(milliseconds: 500));
  return 20 + id;
}

// Mehrere asynchrone Operationen sequenziell ausführen
Future<void> printUserInfo(int id) async {
  print('Beginne mit dem Abrufen der Benutzerinformationen...');

  // Warte nacheinander: zuerst den Namen abrufen, dann das Alter abrufen
  var name = await fetchUserName(id);
  print('Name abgerufen: $name');

  var age = await fetchUserAge(id);
  print('Alter abgerufen: $age');

  print('BEISPIEL Benutzer: $name, Alter: $age');
}

// Mehrere asynchrone Operationen gleichzeitig ausführen
Future<void> printUserInfoParallel(int id) async {
  print(Beginne mit dem gleichzeitigen Abrufen der Benutzerinformationen...);

  // Zwei Anfragen gleichzeitig starten, ohne aufeinander zu warten
  var nameFuture = fetchUserName(id);
  var ageFuture = fetchUserAge(id);

  // Warte, bis beide Anfragen abgeschlossen sind
  var results = await Future.wait([nameFuture, ageFuture]);
  var name = results[0] as String;
  var age = results[1] as int;

  print(BEISPIEL Benutzer (gleichzeitig): $name, Alter: $age);
}

void main() async {
  // Sequentielle Ausführung (Gesamtzeit ≈ 1s + 0.5s = 1.5s)
  await printUserInfo(1);

  print('---');

  // Gleichzeitige Ausführung (Gesamtzeit ≈ max(1s, 0.5s) = 1s)
  await printUserInfoParallel(2);
}
开始获取用户信息...
获取到名称: 用户1
获取到年龄: 21
EXAMPLE 用户: 用户1, 年龄: 21
---
开始并发获取用户信息...
EXAMPLE 用户(并发): 用户2, 年龄: 22

Wenn mehrere asynchrone Operationen keine Abhängigkeiten zwischen sich haben, sollten Sie Future.wait verwenden, um sie gleichzeitig auszuführen, anstatt sie einzeln abzuwarten. Dies kann die Leistung erheblich verbessern — die Gesamtzeit entspricht der langsamsten Operation, nicht der Summe aller Operationen.

Kernregeln von async/await

RegelnBeschreibung
async-Funktionen geben ein Future zurückSelbst wenn die Funktion T zurückgibt, wird sie nach Verwendung von async automatisch in Future<T> verpackt.
await kann nur innerhalb von async-Funktionen verwendet werden.Normale Funktionen können await nicht verwenden
await pausiert die aktuelle FunktionAber es blockiert nicht die Ausführung von anderem Code
await erhält das Ergebnis des FutureWenn das Future einen Fehler hat, wirft await eine Exception.

then / catchError verketteter Aufruf

Zusätzlich zu async/await unterstützt Dart auch verkettete Aufrufe mit then() und catchError().

Dies ist die traditionelle Schreibweise im Promise-Stil.

Beispiel

// Funktion zum Simulieren einer Netzwerkanfrage
Future<String> fetchData(String url) {
  return Future.delayed(Duration(seconds: 1), () {
    if (url.isEmpty) {
      throw Exception(URL darf nicht leer sein);
    }
    return Daten von $url;
  });
}

void main() {
  print(Anfrage wird gestartet...);

  // then / catchError verketteter Aufruf
  fetchData('https://example.com/api')
      .then((data) {
        // Anfrage erfolgreich
        print(Daten erhalten: $data);
        return Verarbeitet: $data;  // Der Rückgabewert wird an das nächste then übergeben
      })
      .then((verarbeitet) {
        // Rückgabewert des vorherigen then behandeln
        print(verarbeitet);
      })
      .catchError((Fehler) {
        // Jeden Fehler in der Kette einheitlich abfangen
        print(Anfragefehler: $error);
      })
      .whenComplete(() {
        // Wird unabhängig von Erfolg oder Fehlschlag ausgeführt (ähnlich wie finally)
        print(Anfrage beendet (unabhängig von Erfolg oder Fehlschlag));
      });

  print(Anfrage wurde gesendet, Programm läuft weiter...);
}
开始请求...
请求已发出,程序继续...
获取到数据: 来自 https://example.com/api 的数据
处理后的: 来自 https://example.com/api 的数据
请求结束(无论成功或失败)

Wie wählt man zwischen async/await und then/catchError?

SzenarienEmpfohlene VorgehensweiseGrund
Linearer asynchroner Ablaufasync/awaitDer Code ist so klar wie synchroner Code
Mehrere gleichzeitige Anfragenasync/await + Future.waitKlare Semantik
Verkettete Datentransformationthen()Jedes then führt einen Transformationsschritt aus, und die Kette ist klar.
Einzelner Callbackthen()Knapper als async/await

In den meisten Fällen wird async/await empfohlen, weil es eine bessere Lesbarkeit bietet. Aber then() ist bei einfacher verketteter Verarbeitung immer noch wertvoll. Die beiden können gemischt werden, aber das wird nicht empfohlen – eine Funktion sollte entweder async/await oder then/catchError verwenden; sie zu mischen macht den Code schwer verständlich.


Behandlung asynchroner Fehler

Bei asynchronen Operationen können Fehler auftreten, daher muss try-catch verwendet werden, um sie zu behandeln.

async/await macht die Fehlerbehandlung so einfach wie synchroner Code.

Beispiel

// Simuliere eine Netzwerkanfrage, die fehlschlagen kann
Future<String> fetchUserProfile(int userId) async {
  await Future.delayed(Duration(seconds: 1));

  if (userId <= 0) {
    throw Exception('Ungültige Benutzer-ID: $userId');
  }

  if (userId == 404) {
    throw HttpException('Benutzer existiert nicht');
  }

  return 'Profil des Benutzers $userId';
}

// Benutzerdefinierte Ausnahme
class HttpException implements Exception {
  final String message;
  HttpException(this.message);

  @override
  String toString() => 'HttpException: $message';
}

Future<void> loadUserProfile(int userId) async {
  print('Profil von Benutzer $userId wird geladen...');

  try {
    var profile = await fetchUserProfile(userId);
    print('Erfolgreich geladen: $profile');
  } on HttpException catch (e) {
    // Bestimmte asynchrone Fehler nach Typ abfangen
    print('HTTP-Fehler: $e');
    print('Den Benutzer auffordern: Bitte prüfen Sie, ob der Benutzer existiert');
  } on Exception catch (e) {
    // Andere Ausnahmen abfangen
    print('Allgemeiner Fehler: $e');
  } finally {
    print('Loading operation ended');
  }
}

void main() async {
  await loadUserProfile(1);    // Erfolg
  print('---');
  await loadUserProfile(404);  // HttpException
  print('---');
  await loadUserProfile(-1);   // Allgemeine Ausnahme
}
正在加载用户 1 的资料...
加载成功: 用户 1 的个人资料
加载操作结束
---
正在加载用户 404 的资料...
HTTP 错误: HttpException: 用户不存在
提示用户:请检查用户是否存在
加载操作结束
---
正在加载用户 -1 的资料...
一般错误: Exception: 无效的用户 ID: -1
加载操作结束

Fehlerbehandlungsregeln in asynchronen Funktionen:

  • Wenn in einer asynchronen Funktion eine Ausnahme ausgelöst wird, wird sie automatisch in den Future eingewickelt.
  • Der Aufrufer kann try-catch verwenden, um die Ausnahme von await abzufangen.
  • Wenn then() verwendet wird, werden Ausnahmen an catchError() übergeben.
  • Nicht abgefangene asynchrone Ausnahmen führen nicht zum Absturz des Programms, aber sie werden als unbehandelter Future-Fehler behandelt.

Ignorieren Sie niemals asynchrone Fehler. Jeder Aufruf einer asynchronen Funktion sollte eine entsprechende Fehlerbehandlung haben. Unbehandelte asynchrone Fehler geben im Entwicklungsmodus Warnungen aus und können in Produktionsumgebungen stillschweigend ignoriert werden, was zu schwer aufzuspürenden Fehlern führt.

Weitere Erweiterungen