Пример async/.await

async/.await - это встроенные в Rust инструменты для написания асинхронного кода, который выглядит как синхронный код. Ключевое слово async преобразует блок кода в конечный автомат, который реализует типаж, называемый Future. В то время как вызов блокирующей функции в синхронном методе заблокирует весь поток, заблокированная Future вернёт контроль над потоком, позволяя работать другим Future.

Для создания асинхронной функции, вы можете использовать синтаксис async fn:


#![allow(unused_variables)]
fn main() {
async fn do_something() { ... }
}

Значение, возвращённоеasync fn является Future. Что бы ни произошло, Future должна быть запущена в исполнителе.

// `block_on` blocks the current thread until the provided future has run to
// completion. Other executors provide more complex behavior, like scheduling
// multiple futures onto the same thread.
use futures::executor::block_on;

async fn hello_world() {
    println!("hello, world!");
}

fn main() {
    let future = hello_world(); // Nothing is printed
    block_on(future); // `future` is run and "hello, world!" is printed
}

Внутри async fn можно использовать .await для ожидания завершения другого типа, реализующего типаж Future (например, полученного из другой async fn). В отличие от block_on, .await не блокирует текущий поток, но асинхронно ждёт завершения футуры, позволяя другим задачам выполняться, если в данный момент футура не может добиться прогресса.

Например, представим что у нас есть три синхронный функции async fn: learn_song, sing_song и dance:


#![allow(unused_variables)]
fn main() {
async fn learn_song() -> Song { ... }
async fn sing_song(song: Song) { ... }
async fn dance() { ... }
}

Одним из способов выполнения функций учить, петь и танцевать будет остановка на каждом из них:

fn main() {
    let song = block_on(learn_song());
    block_on(sing_song(song));
    block_on(dance());
}

Тем не менее, в этом случае мы не получаем наилучшей производительности - в один момент мы делаем только одно дело! Очевидно, что мы должны выучить песню до того, как петь её, но мы можем танцевать в то же время, пока учим песню и поём её. Чтобы сделать это, мы создадим две отдельные async fn, которые могут запуститься параллельно:

async fn learn_and_sing() {
    // Wait until the song has been learned before singing it.
    // We use `.await` here rather than `block_on` to prevent blocking the
    // thread, which makes it possible to `dance` at the same time.
    let song = learn_song().await;
    sing_song(song).await;
}

async fn async_main() {
    let f1 = learn_and_sing();
    let f2 = dance();

    // `join!` is like `.await` but can wait for multiple futures concurrently.
    // If we're temporarily blocked in the `learn_and_sing` future, the `dance`
    // future will take over the current thread. If `dance` becomes blocked,
    // `learn_and_sing` can take back over. If both futures are blocked, then
    // `async_main` is blocked and will yield to the executor.
    futures::join!(f1, f2);
}

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

В этом примере, изучение песни должно быть завершено до пения, но и изучение и пение могут завершиться одновременно с танцем. Если мы использовали бы block_on(learn_song() ) вместо learn_song().await внутри learn_and_sing, поток не смог бы делать ничего другого, пока работает learn_song. Из-за этого мы одновременно с этим не можем танцевать. С помощью ожидания .await футуры learn_song, мы разрешаем другим задачам захватить текущий поток, пока learn_song заблокирована. Это делает возможным запуск нескольких футур, завершающихся параллельно в одном потоке.

Теперь мы изучили основы async/.await, давайте посмотрим их в действии.