async
/await
В первой главе мы бросили беглый взгляд на async
/.await
и использовали
это чтобы построить простой сервер. В этой главе мы обсудим async
/.await
более подробно, объясняя, как это работает и как async
код отличается от
традиционных программ на Rust.
async
/.await
- это специальный синтаксис Rust, который позволяет передавать контроль выполнения в потоке другому коду, пока ожидается окончание завершения, а не блокировать поток.
Существует два основных способа использования async
: async fn
и async
блоки.
Каждый возвращает значение, реализующее типаж Future
:
# #![allow(unused_variables)] #fn main() { // `foo()` returns a type that implements `Future<Output = u8>`. // `foo().await` will result in a value of type `u8`. async fn foo() -> u8 { 5 } fn bar() -> impl Future<Output = u8> { // This `async` block results in a type that implements // `Future<Output = u8>`. async { let x: u8 = foo().await; x + 5 } } #}
Как мы видели в первой главе, async
блоки и другие futures
ленивы:
они ничего не делают, пока их не запустят. Наиболее распространённый способ запуска Future
-
это .await
. Когда .await
вызывается на Future
, он пытается завершить выполнение до конца. Если Future
заблокирована, то контроль будет передан текущему потоку. Чтобы добиться большего прогресса, будет выбрана верхняя Future
исполнителя, позволяя .await
продолжить работу.
Времена жизни async
В отличие от традиционных функций, async fn
, которые принимают ссылки или другие
не-'static
аргументы, возвращают Future
, которая ограничена временем жизни
аргумента:
# #![allow(unused_variables)] #fn main() { // This function: async fn foo(x: &u8) -> u8 { *x } // Is equivalent to this function: fn foo_expanded<'a>(x: &'a u8) -> impl Future<Output = u8> + 'a { async move { *x } } #}
Это означает, что future
, возвращаемая из async fn
, должен быть вызван .await
до тех пор пока её не-'static
аргументы все ещё действительны. В общем
случае, вызов .await
у future
сразу после вызова функции
(как в foo(&x).await
) это не проблема. Однако, если сохранить future
или отправить её в другую задачу или поток, это может быть проблемой.
Один общий обходной путь для включения async fn
со ссылками в аргументах
в 'static
future
состоит в том, чтобы связать аргументы с вызовом
async fn
внутри async
блока:
# #![allow(unused_variables)] #fn main() { fn bad() -> impl Future<Output = u8> { let x = 5; borrow_x(&x) // ERROR: `x` does not live long enough } fn good() -> impl Future<Output = u8> { async { let x = 5; borrow_x(&x).await } } #}
Перемещая аргумент в async
блок, мы продлеваем его время жизни до времени жизни Future
, которая возвращается при вызове foo
.
async move
async
блоки и замыкания позволяют использовать ключевое слово move
, как обычные
замыкания. async move
блок получает владение переменными со ссылками, позволяя им пережить текущую область, но отказывая им в возможности делиться этими
переменными с другим кодом:
# #![allow(unused_variables)] #fn main() { /// `async` block: /// /// Multiple different `async` blocks can access the same local variable /// so long as they're executed within the variable's scope async fn blocks() { let my_string = "foo".to_string(); let future_one = async { // ... println!("{}", my_string); }; let future_two = async { // ... println!("{}", my_string); }; // Run both futures to completion, printing "foo" twice: let ((), ()) = futures::join!(future_one, future_two); } /// `async move` block: /// /// Only one `async move` block can access the same captured variable, since /// captures are moved into the `Future` generated by the `async move` block. /// However, this allows the `Future` to outlive the original scope of the /// variable: fn move_block() -> impl Future<Output = ()> { let my_string = "foo".to_string(); async move { // ... println!("{}", my_string); } } #}
.await
в многопоточном исполнителе
Обратите внимание, что при использовании Future
в многопоточном исполнителе, Future
может перемещаться
между потоками, поэтому любые переменные, используемые в телах async
, должны иметь возможность перемещаться
между потоками, как и любой .await
потенциально может привести к переключению на новый поток.
Это означает, что не безопасно использовать Rc
, &RefCell
или любые другие типы,
не реализующие типаж Send
(включая ссылки на типы, которые не реализуют типаж Sync
).
(Предостережение: можно использовать эти типы до тех пор, пока они не находятся в области действия
вызова .await
.)
Точно так же не очень хорошая идея держать традиционную non-futures-aware
блокировку
через .await
, так как это может привести к блокировке пула потоков: одна задача может
получить объект блокировки, вызвать .await
и передать управление исполнителю, разрешив другой задаче совершить попытку взять блокировку, что и вызовет взаимоблокировку. Чтобы избежать этого, используйте Mutex
из futures::lock
, а не из std::sync
.