前言#
昨天我们知道了异步执行的流程/原理
今天我们可以开始学怎么用了
async/.await#
在之前day1的引子以及昨天的定时器例子中我们有使用到它俩,今天我们再来看下细节。
和同步的堵塞代码不同,异步可以绕开堵塞的问题,让线程先绕过这块事件先去执行别的。
我么来看下例子
// `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
}
} 这是个简单的场景。
注意异步函数返回的内容不一定是Future<output>类型的,如果不搭配.await的话。
可以类比前端async/await的用法,在不用await的时候返回的是一个promise类型的数据。
我们上一节知道这个过程是lazy的,只有被wake的task才开始干活。
如果它执行时判断不是Ready状态,这个task的future就会让出当前线程的控制权,让它去被别的任务占有,直到它所在的task被唤醒,又开始争夺正宫的位置。
async的生命周期#
和传统的函数不同,异步函数默认会将引用或者非'static的参数作为返回类型Feture<output>的生命周期(逻辑与运算,作为lifetime bound)
比如
// 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 }
}一般上下文中是不太需要担心的,但是如果涉及到多线程之间或者多任务之间,那就比较复杂了。
将引用作为参数的异步数转换为'static Future的一种常见解决方法是将参数与异步块中对异步函数的调用捆绑在一起:
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
}
}我们把x和异步函数绑到了一起,这样这个x的引用的作用域就和异步函数调用的作用域一样了。
async move#
异步块(block)/闭包都可以用move这个关键字,和常规闭包有些像。一个异步move的块将会获取引用变量的所有权,它允许这个变量的生命周期比当前block的长,但是不允许它被其他代码使用。
来看个例子
/// `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完返回了,既然block都没了也自然被释放了。
多线程执行器中使用.await#
我们之前有提到过这一点,在多线程的场景下,一个future在不同线程之前穿插。这就需要保证这个future依赖的数据也是能穿插的,也就是实现Send和Sync这俩trait。
如果没有,那这将会是unsafe的。即使是加锁,加锁还有可能造成死锁。
为了避免这些场景,尽量用futures::lock里的Mutex替代std::sync的Mutex。
总结#
今天的好像还是理论偏多。。。。。

参考#
- ^async/.await https://rust-lang.github.io/async-book/03_async_await/01_chapter.html#asyncawait
- ^async-lifetimes https://rust-lang.github.io/async-book/03_async_await/01_chapter.html#async-lifetimes
- ^async move https://rust-lang.github.io/async-book/03_async_await/01_chapter.html#async-move
- ^.await-on-multithread-executor https://rust-lang.github.io/async-book/03_async_await/01_chapter.html#awaiting-on-a-multithreaded-executor
发布于 2023-01-29 23:30・IP 属地广东
