Einführung in die asynchrone Programmierung in Rust
Asynchrone Programmierung ist ein Paradigma, das es ermöglicht, Ein-/Ausgabeoperationen (Netzwerkanfragen, Dateilesen, Datenbankarbeit) ohne Blockierung des Hauptausführungsstrangs durchzuführen. In Rust wird Asynchronität durch das Future-System und die Schlüsselwörter async/await umgesetzt. Im Gegensatz zur Multithreading arbeitet asynchroner Code in einem einzigen Thread und wechselt während Wartezeiten zwischen Aufgaben.
In dieser Anleitung behandeln wir:
- Grundlagen von
async/awaitin Rust - Arbeit mit der Crate
tokio– der beliebtesten asynchronen Laufzeitumgebung - Erstellung und Kombination von
Future - Praktische Beispiele: asynchroner HTTP-Client und Timer
Der Artikel richtet sich an Entwickler, die mit der grundlegenden Syntax von Rust vertraut sind. Wenn Sie gerade erst anfangen, empfehlen wir, zunächst die Grundlagen der Sprache auf PythonLib zu lernen.
Grundlagen von async/await und Future in Rust
Was ist Future?
Future ist ein Trait, der eine asynchrone Operation darstellt, die in der Zukunft abgeschlossen sein kann. In Rust ist Future faul: Es tut nichts, bis es abgefragt (polled) wird. Für die Abfrage ist die asynchrone Laufzeitumgebung (z. B. Tokio) verantwortlich.
Ein einfaches Beispiel einer asynchronen Funktion:
use std::time::Duration;
async fn say_hello() { println!("Hallo aus der asynchronen Welt!"); tokio::time::sleep(Duration::from_secs(1)).await; println!("1 Sekunde ist vergangen");}
#[tokio::main]async fn main() { say_hello().await;}Das Schlüsselwort async verwandelt die Funktion in einen Future. Der Aufruf von .await pausiert die Ausführung der aktuellen Aufgabe, bis der Future abgeschlossen ist, ohne den Thread zu blockieren.
Lebenszyklus einer asynchronen Aufgabe
Wenn Sie .await aufrufen, geschieht Folgendes:
- Die Laufzeitumgebung fragt den Future ab (ruft die Methode
poll()auf) - Wenn der Future bereit ist – wird das Ergebnis zurückgegeben
- Wenn der Future wartet (z. B. auf eine Netzwerkoperation) – wechselt die Laufzeitumgebung zu einer anderen Aufgabe
- Wenn die Ressource verfügbar wird, setzt die Laufzeitumgebung die Aufgabe fort
Eine wichtige Besonderheit von Rust: .await kann nur innerhalb einer asynchronen Funktion oder eines async {}-Blocks aufgerufen werden. Der Versuch, es in einem synchronen Kontext zu verwenden, führt zu einem Kompilierungsfehler.
Praktische Arbeit mit Tokio
Installation und Einrichtung
Tokio ist eine asynchrone Laufzeitumgebung für Rust, die einen Aufgabenplaner, Timer, Netzwerk-Sockets und vieles mehr bereitstellt. Fügen Sie zunächst Folgendes zu Cargo.toml hinzu:
[dependencies]tokio = { version = "1", features = ["full"] }Das Flag features = ["full"] aktiviert alle Funktionen von Tokio. Für die Produktion ist es besser, nur die benötigten Features auszuwählen.
Starten mehrerer asynchroner Aufgaben
Einer der Hauptvorteile der Asynchronität ist die parallele Ausführung vieler Aufgaben. Verwenden Sie tokio::spawn, um Aufgaben im Hintergrund zu starten:
use tokio::time::{sleep, Duration};
async fn task(name: &str, seconds: u64) { println!("Aufgabe {} gestartet", name); sleep(Duration::from_secs(seconds)).await; println!("Aufgabe {} nach {} Sekunden abgeschlossen", name, seconds);}
#[tokio::main]async fn main() { let handle1 = tokio::spawn(task("A", 2)); let handle2 = tokio::spawn(task("B", 1));
// Warten auf den Abschluss beider Aufgaben let _ = tokio::join!(handle1, handle2); println!("Alle Aufgaben erledigt");}Das Makro tokio::join! wartet auf den Abschluss aller übergebenen Futures. Wenn eine Aufgabe früher endet, wird die Ausführung nicht unterbrochen – wir warten auf die anderen.