🚧 Attention, peinture fraîche !

Cette page a été traduite par une seule personne et n'a pas été relue et vérifiée par quelqu'un d'autre ! Les informations peuvent par exemple être erronées, être formulées maladroitement, ou contenir d'autres types de fautes.

Vous pouvez contribuer à l'amélioration de cette page sur sa Pull Request.

Introduction à async et await

Le async et await sont les outils intégrés dans Rust pour écrire des fonctions asynchrones qui ressemblent à du code synchrone. async transforme un bloc de code en une machine à états qui implémente le trait Future. Alors que l'appel à une fonction bloquante dans une méthode synchrone va bloquer tout le processus, les Futures bloquées céderont le contrôle du processus, permettant aux autres Futures de s'exécuter.

Ajoutons quelques dépendances au fichier Cargo.toml :

[dependencies]
futures = "0.3"

Pour créer une fonction asynchrone, vous pouvez utiliser la syntaxe async fn :


#![allow(unused)]
fn main() {
async fn faire_quelquechose() { /* ... */ }
}

La valeur retournée par async fn est une Future. Pour que quelque chose se produise, la Future a besoin d'être exécutée avec un exécuteur.

// `block_on` bloque le processus en cours jusqu'à ce que la future qu'on lui
// donne ait terminé son exécution. Les autres exécuteurs ont un comportement
// plus complexe, comme par exemple ordonnancer plusieurs futures sur le même
// processus.
use futures::executor::block_on;

async fn salutations() {
    println!("salutations !");
}

fn main() {
    let future = salutations(); // rien n'est pas affiché
    block_on(future); // `future` est exécuté et "salutations !" est affiché
}

Dans une async fn, vous pouvez utiliser .await pour attendre la fin d'un autre type qui implémente le trait Future, comme le résultat d'une autre async fn. Contrairement à block_on, .await ne bloque pas le processus en cours, mais attends plutôt de manière asynchrone que la future se termine, pour permettre aux autres tâches de s'exécuter si cette future n'est pas en mesure de progresser actuellement.

Par exemple, imaginons que nous ayons trois async fn : apprendre_chanson, chanter_chanson, et danser :

async fn apprendre_chanson() -> Chanson { /* ... */ }
async fn chanter_chanson(chanson: Chanson) { /* ... */ }
async fn danser() { /* ... */ }

Une façon d'apprendre, chanter, et danser serait de bloquer sur chacun :

fn main() {
    let chanson = block_on(apprendre_chanson());
    block_on(chanter_chanson(chanson));
    block_on(danser());
}

Cependant, nous ne profitons pas de performances optimales de cette manière — nous ne faisons qu'une seule chose à fois ! Il faut que nous apprenions la chanson avant de pouvoir la chanter, mais il reste possible de danser en même temps qu'on apprends et qu'on chante la chanson. Pour pouvoir faire cela, nous pouvons créer deux async fn qui peuvent être exécutés en concurrence :

async fn apprendre_et_chanter() {
    // Attends (await) que la chanson soit apprise avant de la chanter.
    // Nous utilisons ici `.await` plutôt que `block_on` pour éviter de bloquer
    // le processus, ce qui rend possible de `danser` en même temps.
    let chanson = apprendre_chanson().await;
    chanter_chanson(chanson).await;
}

async fn async_main() {
    let f1 = apprendre_et_chanter();
    let f2 = danser();

    // `join!` se comporte comme `.await`, mais permet d'attendre plusieurs
    // futures en concurrence. Si nous avions bloqué temporairement dans la
    // future `apprendre_et_chanter`, la future `danser` aurais pris le relais
    // dans le processus d'exécution en cours. Si `danser` se bloque aussi,
    // `apprendre_et_chanter` pourra continuer dans le processus en cours. Si
    // les deux futures sont bloquées, et bien `async_main` est bloqué et va en
    // informer son exécuteur.
    futures::join!(f1, f2);
}

fn main() {
    block_on(async_main());
}

Dans cet exemple, la chanson doit être apprise avant de chanter la chanson, mais l'apprentissage et le chant peuvent se dérouler en même temps qu'on danse. Si nous avions utilisé block_on(apprendre_chanson()) plutôt que apprendre_chanson().await dans apprendre_et_chanter, le processus n'aurait rien pu faire tant que apprendre_chanson s'exécutait. Cela aurait rendu impossible de pouvoir danser en même temps. En attendant la future apprendre_chanson, grâce à await, nous permettons aux autres tâches de prendre le relais dans le processus en cours d'exécution lorsque apprendre_chanson est bloqué. Cela permet d'exécuter plusieurs futures jusqu'à leur fin de manière concurrente au sein du même processus.