Async/Await in Rust verstehen
James Reed
Infrastructure Engineer · Leapcell

Async/.await in Rust Asynchrone Programmierung
async/.await ist eine in Rust eingebaute Sprachfunktion, die es uns ermöglicht, asynchronen Code in einem synchronen Stil zu schreiben.
Lernen wir anhand von Beispielen, wie man die Schlüsselwörter async/.await verwendet. Bevor wir beginnen, müssen wir das Paket futures vorstellen. Bearbeiten Sie die Datei Cargo.toml und fügen Sie den folgenden Inhalt hinzu:
[dependencies] futures = "0.3"
Verwenden von async zum Erstellen einer asynchronen Future
Einfach ausgedrückt kann das Schlüsselwort async verwendet werden, um die folgenden Arten von Future zu erstellen:
- Eine Funktion definieren:
async fn - Einen Block definieren:
async {}
Zum Beispiel eine async-Funktion:
async fn hello_world() { ... }
Das Schlüsselwort async modifiziert den Funktionsprototyp so, dass er ein Future-Trait-Objekt zurückgibt. Es kapselt dann das Ausführungsergebnis in ein neues Future und gibt es zurück, was ungefähr dem Folgenden entspricht:
fn hello_world() -> impl Future<Output = ()> { async { ... } }
Hinweis: Der
async-Block implementiert ein anonymesFuture-Trait-Objekt, das einenGeneratoreinkapselt, welcher einFutureimplementierender Generator ist. EinGeneratorfungiert im Wesentlichen als Zustandsautomat. Wenn eine Operation innerhalb einesasync-BlocksPoll::Pendingzurückgibt, ruft der Generatoryieldauf und gibt die Ausführung ab. Nach der Wiederaufnahme setzt der Generator die Ausführung fort, bis der gesamte Code abgeschlossen ist, was bedeutet, dass der Zustandsautomat in den ZustandCompleteeintritt undPoll::Readyzurückgibt, was signalisiert, dass die Ausführung desFutureabgeschlossen ist.
Ein mit async markierter Codeblock wird in einen Zustandsautomaten umgewandelt, der das Future-Trait implementiert. Im Gegensatz zu synchronen Aufrufen, die den aktuellen Thread blockieren, gibt ein Future, wenn er auf eine blockierende Operation trifft, die Kontrolle über den aktuellen Thread ab und wartet auf das Ausführungsergebnis anderer Futures.
Ein Future muss auf einem Executor laufen. Zum Beispiel ist block_on ein Executor, der den aktuellen Thread blockiert:
// block_on blockiert den aktuellen Thread, bis die angegebene Future die Ausführung abgeschlossen hat. // Dieser Ansatz ist einfach und direkt, aber andere Runtime-Executors bieten komplexere Verhaltensweisen, // wie z.B. die Verwendung von join, um mehrere Futures auf demselben Thread zu planen. use futures::executor::block_on; async fn hello_world() { println!("hello, world!"); } fn main() { let future = hello_world(); // Gibt eine Future zurück, daher wird noch keine Ausgabe gedruckt block_on(future); // Führt die Future aus und wartet auf deren Abschluss; dann wird "hello, world!" gedruckt }
Verwenden von await zum Warten auf den Abschluss einer anderen asynchronen Future
In der obigen main-Funktion haben wir den block_on-Executor verwendet, um auf den Abschluss der Future zu warten, wodurch der Code synchron erscheint. Aber was ist, wenn Sie eine async fn innerhalb einer anderen async fn aufrufen und auf deren Abschluss warten müssen, bevor Sie nachfolgenden Code ausführen? Zum Beispiel:
use futures::executor::block_on; async fn hello_world() { // Direkter Aufruf einer anderen Async-Funktion innerhalb einer Async-Funktion - wird das funktionieren? hello_cat(); println!("hello, world!"); } async fn hello_cat() { println!("hello, kitty!"); } fn main() { let future = hello_world(); block_on(future); }
Hier rufen wir in der async-Funktion hello_world zuerst eine andere Async-Funktion hello_cat auf und geben dann "hello, world!" aus. Überprüfen wir die Ausgabe:
warning: unused implementer of `futures::Future` that must be used --> src/main.rs:6:5 | 6 | hello_cat(); | ^^^^^^^^^^^^ = note: futures do nothing unless you `.await` or poll them ... hello, world!
Wie erwartet haben wir die Future in main mit block_on ausgeführt, aber die von hello_cat zurückgegebene Future wurde nie ausgeführt. Glücklicherweise gibt der Compiler eine freundliche Warnung aus: "Futures do nothing unless you .await or poll them."
Es gibt zwei Lösungen:
- Verwenden Sie die
.await-Syntax. - Fragen Sie die
Futuremanuell ab (was komplexer ist, daher werden wir es hier nicht behandeln).
Ändern wir den Code mit .await:
use futures::executor::block_on; async fn hello_world() { hello_cat().await; println!("hello, world!"); } async fn hello_cat() { println!("hello, kitty!"); } fn main() { let future = hello_world(); block_on(future); }
Nach dem Hinzufügen von .await zu hello_cat() ändert sich die Ausgabe erheblich:
hello, kitty! hello, world!
Die Ausgabereihenfolge folgt nun strikt der Code-Reihenfolge. Dies bedeutet, dass wir eine asynchrone Ausführung erreicht haben, während wir einen sequenziellen Codierungsstil beibehalten haben. Dieser Ansatz ist einfach, effizient und eliminiert Callback-Höllen.
Intern wirkt jedes .await wie ein Executor, der den Future-Zustand wiederholt abfragt. Wenn es Pending zurückgibt, ruft es yield auf. Andernfalls beendet es die Schleife und schließt die Future-Ausführung ab. Die Logik ist ungefähr wie folgt:
loop { match some_future.poll() { Pending => yield, Ready(x) => break } }
Kurz gesagt, die Verwendung von .await innerhalb einer async fn ermöglicht das Warten auf den Abschluss eines anderen asynchronen Aufrufs. Anders als block_on blockiert .await jedoch nicht den aktuellen Thread. Stattdessen wartet er asynchron auf den Abschluss von Future A. Während des Wartens kann der Thread die Ausführung anderer Future B-Instanzen fortsetzen, wodurch die Parallelität ermöglicht wird.
Ein Beispiel
Betrachten Sie ein Szenario von Singen und Tanzen. Ohne .await könnte die Implementierung wie folgt aussehen:
use futures::executor::block_on; struct Song { author: String, name: String, } async fn learn_song() -> Song { Song { author: "Rick Astley".to_string(), name: String::from("Never Gonna Give You Up"), } } async fn sing_song(song: Song) { println!( "Performing {}'s {} ~ {}", song.author, song.name, "Never gonna let you down" ); } async fn dance() { println!("Dancing along to the song"); } fn main() { let song = block_on(learn_song()); // Erster blockierender Aufruf block_on(sing_song(song)); // Zweiter blockierender Aufruf block_on(dance()); // Dritter blockierender Aufruf }
Dieser Code läuft korrekt, erfordert aber drei aufeinanderfolgende blockierende Aufrufe, wobei jeweils eine Aufgabe erledigt wird. In Wirklichkeit könnten wir gleichzeitig singen und tanzen:
use futures::executor::block_on; struct Song { author: String, name: String, } async fn learn_song() -> Song { Song { author: "Rick Astley".to_string(), name: String::from("Never Gonna Give You Up"), } } async fn sing_song(song: Song) { println!( "Performing {}'s {} ~ {}", song.author, song.name, "Never gonna let you down" ); } async fn dance() { println!("Dancing along to the song"); } async fn learn_and_sing() { let song = learn_song().await; sing_song(song).await; } async fn async_main() { let f1 = learn_and_sing(); let f2 = dance(); // Das join!-Makro führt mehrere Futures gleichzeitig aus futures::join!(f1, f2); } fn main() { block_on(async_main()); }
Hier haben Lernen und Singen eine strikte Reihenfolge, aber beides kann mit Tanzen koexistieren. Ohne .await würde block_on(learn_song()) den aktuellen Thread blockieren und alle anderen Aufgaben, einschließlich Tanzen, verhindern.
Daher ist .await entscheidend für die asynchrone Programmierung in Rust. Es ermöglicht die gleichzeitige Ausführung mehrerer Aufgaben auf demselben Thread, anstatt sie sequenziell auszuführen.
Fazit
async/.await ist Rusts eingebautes Werkzeug zum Schreiben asynchroner Funktionen, die wie synchroner Code aussehen. async konvertiert einen Codeblock in einen Zustandsautomaten, der das Future-Trait implementiert, das auf einem Executor laufen muss. Anstatt einen ganzen Thread zu blockieren, gibt ein Future die Kontrolle ab, sodass andere Futures ausgeführt werden können.
Wesentliche Erkenntnisse:
Futurerepräsentiert eine Aufgabe, die in der Zukunft einen Wert liefert.asyncerstellt eineFuture..awaitfragt eineFutureab und wartet auf deren Abschluss.- Executors (wie
block_on) verwalten und führenFutures aus. - Rusts
asyncist ohne Kosten: keine Heap-Allokation oder dynamischer Versand. - Rust enthält keine integrierte Async-Runtime; Drittanbieterbibliotheken wie
tokio,async-stdundsmolstellen diese Funktionalität bereit.
Zusammenfassend lässt sich sagen, dass async/.await eine effiziente, gleichzeitige Aufgabenausführung in Rust ermöglicht, Callback-Höllen eliminiert und die asynchrone Programmierung intuitiv macht.
Wir sind Leapcell, Ihre erste Wahl für das Hosten von Rust-Projekten.
Leapcell ist die Next-Gen Serverless Plattform für Webhosting, asynchrone Aufgaben und Redis:
Multi-Sprachen Unterstützung
- Entwickeln Sie mit Node.js, Python, Go oder Rust.
Stellen Sie unbegrenzt Projekte kostenlos bereit
- Zahlen Sie nur für die Nutzung - keine Anfragen, keine Gebühren.
Unschlagbare Kosteneffizienz
- Pay-as-you-go ohne ungenutzte Gebühren.
- Beispiel: 25 US-Dollar unterstützen 6,94 Millionen Anfragen bei einer durchschnittlichen Antwortzeit von 60 ms.
Optimierte Entwicklererfahrung
- Intuitive Benutzeroberfläche für mühelose Einrichtung.
- Vollständig automatisierte CI/CD-Pipelines und GitOps-Integration.
- Echtzeitmetriken und -protokollierung für umsetzbare Erkenntnisse.
Mühelose Skalierbarkeit und hohe Leistung
- Automatische Skalierung zur einfachen Bewältigung hoher Parallelität.
- Null Betriebsaufwand – konzentrieren Sie sich einfach auf den Aufbau.
Erfahren Sie mehr in der Dokumentation!
Folgen Sie uns auf X: @LeapcellHQ



