前言#
昨天我们学了两个并发相关的异步宏join!和select!
官方文档有一大部分都是TODO状态的,以后有了再补充吧
了解和爱上变通方案(workarounds and to know and love)#
这翻译对不对的。。。不对麻烦指正下,机翻则是:了解和喜爱的解决方法
你们应该也注意到了这本文档和之前入门的文档差距是比较大的。
很大的原因是异步这一块还处于完善过程中。
所以有些功能都不齐全,更别说文档了。
接下来我们会接触到一些比较恶心的点,并了解到如何绕过它们
? in async Blocks#
我们可以在async function中使用?操作符传递错误。
但是在async block就不行了,因为没地方给我们声明return的类型。这样编译器不知道啥类型直接就给你报错了。
let fut = async {
foo().await?;
bar().await?;
Ok(())
};会遇到下面这个错误
error[E0282]: type annotations needed
--> src/main.rs:5:9
|
4 | let fut = async {
| --- consider giving `fut` a type
5 | foo().await?;
| ^^^^^^^^^^^^ cannot infer type那要怎么做呢?给fut设置个类型?
use futures::{Future, executor::block_on};
async fn foo () -> Result<u8, ()> { Ok(1) }
async fn bar () -> Result<u8, ()> { Ok(2) }
fn main () {
let fut: Box<dyn Future<Output = Result<u8, ()>>> = Box::new(async {
foo().await?;
bar().await?;
Ok(1)
});
block_on(fut);
} 但是还是会报错,

手动给fut解引用也是不行的,不过这里有推荐使用Box::pin,所以我们来用下
use std::pin::Pin;
use futures::{Future, executor::block_on};
async fn foo () -> Result<u8, String> { Err("i am wrong".to_string()) }
async fn bar () -> Result<u8, String> { Ok(2) }
fn main () {
let fut: Pin<Box<dyn Future<Output = Result<u8, String>>>> = Box::pin(async {
foo().await?;
bar().await?;
Ok(1)
});
let res = block_on(fut);
dbg!(res);
}再来运行下

这样确实可以啦,但是可以看到类型也变得非常繁琐。
实际上官方还推荐了另一种方式,我们直接来改下
let fut = async {
foo().await?;
bar().await?;
Ok::<(), String>(()) // <- note the explicit type annotation here
};这样就变得优雅多了。
Send Approximation#
一些异步函数的状态机能安全的在不同线程之间穿插,但是有些不行。
一个异步函数的Future是否是实现了Send的取决于一个没有实现Send的类型是否在这个await的过程中保留。编译器会在这个过程中尽自己最大的努力去保持这个值安全,但是它的分析还是太过保守了。
先来看个例子
use std::rc::Rc;
#[derive(Default)]
struct NotSend(Rc<()>);这就是个non-Send的类型,它里面包含了Rc这个类型,学多线程那一章的时候我们知道了Rc是不安全的,有可能导致计数出错引起内存泄漏,得用Arc在多线程中。
这个类型一般会作为临时变量出现在返回类型是实现了Send的future的异步函数当中:
async fn bar() {}
async fn foo() {
NotSend::default();
bar().await;
}
fn require_send(_: impl Send) {}
fn main() {
require_send(foo());
}这么写是没有问题的,但是如果你将default()赋值给一个变量,那么就会报错了
async fn foo() {
let x = NotSend::default();
bar().await;
}
为什么会报错呢?
两者的区别就在于是否用临时变量存储了这个数据。
当我们存储了这个数据,并且是在.await之前,那么这个变量在.await之前就不会被drop,这也就意味着这个变量的数据和这个.await的生命周期基本一致。
当这个异步函数被移动到别的线程时,这个临时变量也会跟着去到别的线程,这样就跨线程了。而前面说过Rc在多线程里是不安全的。
你应该想到了下面几种处理方案:
- 调用
drop方法手动先drop掉它(不可行,依旧会报错) - 调换位置,把这个变量放到
.await之后(可行,但是有点避开问题的嫌疑) - 提供一个作用域包裹这个临时变量的使用上下文(可行,这个方案和
1的类似,都是先释放掉这个临时变量) - 优化逻辑,确定这个变量是否真有必要存在
官方推荐的是方案3。
async fn foo() {
{
let x = NotSend::default();
}
bar().await;
}Recursion#
当我们写完一堆异步函数之后,编译器实际上会做处理。把这些个异步函数组装到一起。
比如:
// This function:
async fn foo() {
step_one().await;
step_two().await;
}
// generates a type like this:
enum Foo {
First(StepOne),
Second(StepTwo),
}
// So this function:
async fn recursive() {
recursive().await;
recursive().await;
}
// generates a type like this:
enum Recursive {
First(Recursive),
Second(Recursive),
}第一个异步函数里面存在俩异步函数调用,编译器会给它创建一个类型Foo,然后有几个异步函数调用就有几个变体。
但如果调用的是异步递归函数,这就会有问题了。
error[E0733]: recursion in an `async fn` requires boxing
--> src/lib.rs:1:22
|
1 | async fn recursive() {
| ^ an `async fn` cannot invoke itself directly
|
= note: a recursive `async fn` must be rewritten to return a boxed future.我们创建了一个无尽大小的类型,编译器自然不给过了。
联想一下我们之前这么处理这种递归类型的,我们当时用的Box将这个类型套住返回一个指针,编译器看到只有一个指针大小,自然就放过了。
那么在异步我们应该得用Box::pin来包裹,但是还不够。
use futures::future::{BoxFuture, FutureExt};
fn recursive() -> BoxFuture<'static, ()> {
async move {
recursive().await;
recursive().await;
}.boxed()
}我们得改成non-async函数,用async block包裹异步递归函数调用,最后返回.boxed。
.boxed在这里相对于Box::pin的语法糖。

不过记住,这只是骗过了编译器,但别把自己骗进去了。你的递归代码是否正常运行你得到runtime去check一下。
async in Traits#
目前,异步函数还不能在trait中实现(2022.11.17有个nightly版本的async-fn-in-trait提供出来,具体请看:https://blog.rust-lang.org/inside-rust/2022/11/17/async-fn-in-trait-nightly.html)。
不过,这里还有绕弯的法子可以实现async-fn-in-trait:https://github.com/dtolnay/async-trait,这个crate提供了一个属性宏async_trait,将它放到你的trait头顶就可以使用了。
至于为啥这么久都没有实现这一块功能,可以看这里:Baby Steps (smallcultfollowing.com)
需要注意的一点,每次调用这些trait的方法时都对触发一次堆内存分配(heap allocation)。
虽然不会占你内存开销大头,但是也是需要注意的一个点,因为rust作为一门比较低等级的语言,你的项目面对的场景可能是非常非常巨大的,比如操作系统级别的,那么你这个异步方法被调用的次数将难以估计。
总结#
没有总结
参考#
- ^workarounds-to-know-and-love https://rust-lang.github.io/async-book/07_workarounds/01_chapter.html#workarounds-to-know-and-love
- ^?-in-async-blocks https://rust-lang.github.io/async-book/07_workarounds/02_err_in_async_blocks.html
- ^Send-Approximation https://rust-lang.github.io/async-book/07_workarounds/03_send_approximation.html
- ^Recursion https://rust-lang.github.io/async-book/07_workarounds/04_recursion.html
- ^async-in-traits https://rust-lang.github.io/async-book/07_workarounds/05_async_in_traits.html
编辑于 2023-02-01 11:41・IP 属地广东
