
原文:Build with Naz : Rust async in practice tokio::select!, actor pattern & cancel safety
tokio::select! 用于并发 poll() 多个 Future,并在首个 Future 状态为 Ready 时将其他分支的 Future drop 掉。就像是不同分支的 Future 在赛跑,只有率先 Ready 的 Future 才能获取值并且执行对应分支。
多个 Future 会在同一线程上并发执行,因为不涉及到 spawn 操作。而由于是单线程并发执行,如果 Future 里面有 CPU 密集型操作(没有 await 让出线程),该 Future 会持续占用线程,不会切换到其它分支执行。
取消安全(Cancellation safety):即使 Future 在 Ready 之前 drop,然后重新创建,对期望执行的操作都无任何影响。
如果操作依赖于 Future 内保存的状态,那就是非取消安全的。非取消安全的 Future 在被 drop 后会丢失状态。
对非取消安全的 Future 进行 drop 并不是错误的行为,除非依赖 Future 里的状态。类似 unsafe 代码,需要编程者手动保证安全。
取消安全的定义及函数清单:https://docs.rs/tokio/latest/tokio/macro.select.html#cancellation-safety
在循环中调用 tokio::select! 时,每次循环都会重新调用一遍分支的代码生成新的 Future 再重新赛跑,而上一次循环中未完成的 Future 会被 drop。
如果是非取消安全的 Future,不想 Future 被 drop 丢失状态,可以使用 &mut Future,这样 drop 的就只是引用,Future 的状态依旧保存。这是由于 impl<F: Future + Unpin + ?Sized> Future for &mut F,Future 的可变引用也是 Future。
对于 async 代码块而言,脱糖的 Future 结构体是一个 !Unpin 的自引用结构,不满足 <F: Future + Unpin + ?Sized> 的 trait 约束,因此需要先 Pin 后再使用 &mut Pin。
对于 async 代码块而言,Future 在 Ready 后再进入 poll() 会导致 panic,因此需要在完成后 break 循环。
#[tokio::test]
async fn test_sleep_right_and_wrong_ways_v1() {
let mut count = 5;
let sleep_time = 100;
let duration = std::time::Duration::from_millis(sleep_time);
let sleep = tokio::time::sleep(duration);
tokio::pin!(sleep);
loop {
tokio::select! {
// Branch 1 (right way)
// 在每次循环中都调用 &mut sleep 创建一个新的 Future,指向循环外部的 sleep。
// 相当于通过指针 poll 同一个 Sleep Future,剩余时间状态不会丢失。
_ = &mut sleep => {
println!("branch 1 - tick : {count}");
count -= 1;
if count == 0 {
break;
}
}
// Branch 2 (wrong way)
// 在每次循环都调用 tokio::time::sleep(duration) 重新创建 Sleep Future。
// 每次循环,剩余时间状态都会丢失并重新创建、从头计数。
_ = tokio::time::sleep(duration) => {
println!("branch 2 - sleep");
}
}
}
}原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。