Travailler avec les futurs et les tâches
Comprenez le trait `Future` et la manière dont les tâches sont planifiées et gérées dans un environnement d’exécution asynchrone.
Travailler avec les futurs et les tâches est une leçon Learn Rust Coding gratuite sur CoddyKit. Ceci est la leçon 3 sur 3. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Learn Rust Coding, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Learn Rust Coding comprend 3 leçons au total.
Qu'est-ce qu'un futur Rust ?
En Rust asynchrone, un Future est un trait qui représente un calcul asynchrone susceptible de s'achever à un moment donné. Considérez-le comme une promesse pour une valeur qui n'est pas encore prête.
- C'est le composant fondamental de Rust asynchrone.
- Les
async fnen Rust renvoient en réalité un type anonyme qui implémente le traitFuture. - La valeur n'est pas calculée immédiatement : elle est calculée lorsque le
Futureest « interrogé » par un exécuteur.
Comment les futurs progressent
Un Future ne s'exécute pas de lui-même. Un exécuteur (comme le moteur d'exécution Tokio) l'« interroge » régulièrement pour vérifier s'il a progressé ou s'il est terminé.
- Lorsqu'il est interrogé, un
Futurerenvoie soitPoll::Pending(pas encore terminé), soitPoll::Ready(T)(terminé, voici le résultat). - En cas de
Pending, l'exécuteur sait qu'il devra l'interroger de nouveau plus tard, lorsqu'un événement pertinent se produira (par exemple, la fin d'une entrée-sortie). - Ce mécanisme d'interrogation permet à de nombreuses opérations asynchrones de s'exécuter simultanément sur un seul fil d'exécution.
Votre code asynchrone est un futur
Lorsque vous écrivez une async fn, Rust la transforme en une machine à états qui implémente le trait Future. Le calcul réel ne commence que lorsque le Future renvoyé est interrogé.
Examinons une fonction async simple. Elle ne s'exécute pas tant qu'elle n'est pas attendue par un exécuteur.
async fn say_hello() -> String {
"Hello from a Future!".to_string()
}
#[tokio::main]
async fn main() {
let future = say_hello(); // This doesn't run the function yet!
println!("Future created, but not awaited.");
// To run it, an executor needs to poll it, often via .await
// let result = future.await;
// println!("{}", result);
}Les futurs deviennent des tâches
Alors qu'un Future définit un calcul asynchrone, une tâche est une instance active de ce Future, menée à son terme par un exécuteur.
- Lorsque vous demandez à un exécuteur (comme Tokio) d'exécuter un
Future, il l'encapsule dans une tâche. - L'exécuteur gère ensuite cette tâche et l'interroge chaque fois qu'elle est prête à progresser.
- Les tâches sont les unités de travail que le moteur d'exécution asynchrone planifie et exécute simultanément.
Créer des tâches pour la concurrence
Pour exécuter un Future simultanément avec un autre code, vous le « créez » dans le moteur d'exécution Tokio. Cela crée une nouvelle tâche que le moteur gérera.
La fonction tokio::spawn prend un Future et renvoie un JoinHandle, que vous pouvez utiliser pour attendre la fin de la tâche et obtenir son résultat.
async fn perform_task(id: u8) -> String {
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
format!("Task {} finished!", id)
}
#[tokio::main]
async fn main() {
println!("Main started.");
let handle = tokio::spawn(async {
perform_task(1).await
});
println!("Task 1 spawned.");
// We will await 'handle' in the next scene to get the result.
// let result = handle.await.unwrap();
// println!("{}", result);
}Obtenir les résultats des tâches
Le JoinHandle renvoyé par tokio::spawn est lui-même un Future. Vous pouvez attendre ce descripteur avec .await afin d'attendre la fin de la tâche créée et de récupérer sa valeur de retour.
Si la tâche créée provoque une panique, l'attente de son JoinHandle renverra une erreur.
async fn perform_task(id: u8) -> String {
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
format!("Task {} finished!", id)
}
#[tokio::main]
async fn main() {
println!("Main started.");
let handle = tokio::spawn(async {
perform_task(1).await
});
println!("Task 1 spawned.");
let result = handle.await.unwrap(); // Await the handle to get the result
println!("{}", result);
println!("Main finished.");
}Plusieurs tâches simultanées
La puissance des tâches et de tokio::spawn se révèle pleinement lorsque vous exécutez plusieurs opérations simultanément. Le moteur d'exécution Tokio bascule efficacement d'une tâche à l'autre à mesure qu'elles deviennent prêtes.
Votre programme peut ainsi progresser sur de nombreuses opérations à la fois sans être bloqué, même sur un seul fil d'exécution.
async fn long_task(id: u8) -> String {
tokio::time::sleep(tokio::time::Duration::from_millis(100 + id as u64 * 50)).await;
format!("Long task {} done!", id)
}
#[tokio::main]
async fn main() {
println!("Main started.");
let handle1 = tokio::spawn(long_task(1));
let handle2 = tokio::spawn(long_task(2));
println!("Both tasks spawned.");
let result1 = handle1.await.unwrap();
let result2 = handle2.await.unwrap();
println!("{}\n{}", result1, result2);
println!("Main finished.");
}Tout attendre avec join!
Lorsque vous devez attendre que plusieurs Futures s'achèvent en même temps, tokio::join! est une macro utile. Elle attend simultanément tous les futurs fournis et renvoie leurs résultats sous forme de tuple.
C'est comparable au fait d'attendre chaque descripteur individuellement, mais cette solution est souvent plus concise pour un nombre fixe de futurs.
async fn fetch_data(source: &str) -> String {
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
format!("Data from {}", source)
}
#[tokio::main]
async fn main() {
println!("Starting data fetches...");
let (data_a, data_b) = tokio::join!(
fetch_data("Server A"),
fetch_data("Database B")
);
println!("Fetched: {}\nFetched: {}", data_a, data_b);
println!("All fetches complete.");
}Gérer les erreurs des tâches
Les opérations asynchrones peuvent échouer, tout comme les opérations synchrones. Il est courant que les Futures renvoient un type Result, indiquant une réussite ou un échec.
Lorsque vous attendez un JoinHandle, n'oubliez pas que le résultat est enveloppé dans un autre Result, car la tâche elle-même peut provoquer une panique. Vous verrez souvent handle.await? (dans une async fn renvoyant un Result) ou handle.await.unwrap() / .expect().
async fn might_fail(should_fail: bool) -> Result<String, &'static str> {
tokio::time::sleep(tokio::time::Duration::from_millis(30)).await;
if should_fail {
Err("Oops, something went wrong!")
} else {
Ok("Operation successful!".to_string())
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let handle_ok = tokio::spawn(might_fail(false));
let handle_err = tokio::spawn(might_fail(true));
let result_ok = handle_ok.await??; // Await handle, then await inner Result
println!("{}", result_ok);
match handle_err.await? { // Await handle, then handle inner Result
Ok(val) => println!("{}", val),
Err(e) => eprintln!("Error: {}", e),
}
Ok(())
}Comprendre les futurs et les tâches
Vous avez découvert les futurs en tant que calculs et les tâches en tant qu'instances qui les exécutent. Quelle affirmation concernant tokio::spawn et JoinHandle est vraie ?
Récapitulatif : futurs et tâches
Bravo ! Vous maîtrisez désormais les concepts fondamentaux des Futures et des tâches dans l'écosystème asynchrone de Rust :
- Un
Futureest un trait représentant un calcul asynchrone qui produira finalement une valeur. - Les
async fnsont compilées en types qui implémentent le traitFuture. - Une tâche est une instance d'un
Futurequ'un exécuteur (comme Tokio) gère et interroge activement. tokio::spawnsert à créer une nouvelle tâche et renvoie unJoinHandle.- Vous attendez un
JoinHandleavec.awaitpour obtenir le résultat d'une tâche créée.
Ces composants sont essentiels pour écrire des applications Rust efficaces et non bloquantes !
Questions Fréquemment Posées
La leçon « Travailler avec les futurs et les tâches » est-elle gratuite ?
Oui — le texte complet de « Travailler avec les futurs et les tâches » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Learn Rust Coding, passe à CoddyKit PRO. Le cours Learn Rust Coding comprend 3 leçons au total.
Qu'est-ce que j'apprendrai dans « Travailler avec les futurs et les tâches » ?
Comprenez le trait `Future` et la manière dont les tâches sont planifiées et gérées dans un environnement d’exécution asynchrone. Tu pratiques Learn Rust Coding avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer Learn Rust Coding ?
Aucune expérience préalable n'est requise. Learn Rust Coding sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 3 sur 3.
Combien de temps prend la leçon « Travailler avec les futurs et les tâches » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon Learn Rust Coding ?
Oui. Chaque leçon Learn Rust Coding inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Introduction à Async/Await
- Créer des applications asynchrones avec Tokio
- Travailler avec les futurs et les tâches