首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >rust select! 与取消安全的 Future

rust select! 与取消安全的 Future

原创
作者头像
莫他喵
发布2025-06-17 10:39:32
发布2025-06-17 10:39:32
2210
举报

原文:Build with Naz : Rust async in practice tokio::select!, actor pattern & cancel safety

1. tokio::select!

tokio::select! 用于并发 poll() 多个 Future,并在首个 Future 状态为 Ready 时将其他分支的 Future drop 掉。就像是不同分支的 Future 在赛跑,只有率先 Ready 的 Future 才能获取值并且执行对应分支。

多个 Future 会在同一线程上并发执行,因为不涉及到 spawn 操作。而由于是单线程并发执行,如果 Future 里面有 CPU 密集型操作(没有 await 让出线程),该 Future 会持续占用线程,不会切换到其它分支执行。

2. 取消安全的 Future

取消安全(Cancellation safety):即使 Future 在 Ready 之前 drop,然后重新创建,对期望执行的操作都无任何影响。

如果操作依赖于 Future 内保存的状态,那就是非取消安全的。非取消安全的 Future 在被 drop 后会丢失状态。

对非取消安全的 Future 进行 drop 并不是错误的行为,除非依赖 Future 里的状态。类似 unsafe 代码,需要编程者手动保证安全。

取消安全的定义及函数清单:https://docs.rs/tokio/latest/tokio/macro.select.html#cancellation-safety

3. 在 loop 中 tokio::select!

在循环中调用 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 循环。

4. 正例 & 反例

代码语言:rust
复制
#[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 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. tokio::select!
  • 2. 取消安全的 Future
  • 3. 在 loop 中 tokio::select!
  • 4. 正例 & 反例
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档