Пример async
/.await
async
/.await
- инструменты для написания асинхронного кода встроенные в Rust, внешне похожие на синхронный код. Ключевое слово async
превращает исполняемый блок программы в конечный автомат, который реализует типаж Future
. В синхронном методе вызов функции блокирует весь поток, в асинхронном же вызов через Future
вернёт контроль над потоком, позволяя работать другим Future
.
Добавим некоторые зависимости в файл Cargo.toml
:
[dependencies]
futures = "0.3"
При реализации асинхронной функции можно использовать такой синтаксис async fn
:
#![allow(unused)] 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
:
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
заблокирована. Это даёт возможность запускать нескольких футур в одном потоке параллельно.