You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何让编译器相信我的异步FnMut不会被并行调用?

如何让编译器相信我的异步FnMut不会被并行调用?

嘿,这个问题我太有共鸣了!先把你的场景和问题掰扯清楚:你有两个通用工具组件——一个是TempFile临时文件包装器,另一个是retry异步重试函数,现在想把依赖可变TempFile的异步逻辑塞到retry里,但编译器总揪着“闭包会被并行调用”的点不放,可你明明知道retry里是等上一次调用完了才会来下一次,根本不会有并行的情况!

先给你吃个定心丸:你写的retry函数本身完全不会并行调用传入的闭包!那个for循环是同步执行的,每次调用func()之后,你立刻就.await等这个异步任务做完才会进入下一次循环,前一个闭包的可变借用早就释放了,根本不会出现同时借用的冲突。

那为啥编译器还炸毛?大概率是它没从泛型约束里看出来你的串行逻辑,或者你传入闭包的写法让它产生了误解。给你几个实用的解决思路:

  • 先检查你的retry实现有没有暗戳戳搞并发:比如你是不是在循环里用tokio::spawn或者async_std::task::spawnfunc()的任务扔后台了?要是有这个操作,那编译器的担心完全合理——这时候确实会并行调用闭包,你得把spawn去掉,改成直接func().await,就像你最初的写法那样。

  • 调整闭包的捕获方式,让编译器看清借用范围:如果你的闭包要用到&mut TempFile(比如调用它的可变方法),尽量让闭包的异步逻辑里,借用只在需要的时候存在。比如别提前把可变引用攥在手里,而是在异步块里直接调用方法:

    let mut temp_file = TempFile::new();
    // 这种写法编译器大概率能看懂
    let result = retry(|| async {
        temp_file.do_something_async().await
    }).await;
    

    因为每次调用闭包时,temp_file的可变借用只存在于这一次do_something_async的Future生命周期里,等await完就自动释放了,下一次循环再借完全没问题。

  • 给编译器“开个小灶”——用注释明确承诺串行语义:你可以在retry函数上方加个醒目的注释,比如:

    /// 严格串行的异步重试函数:每次调用传入的闭包前,都会等待上一次的异步任务完全结束
    /// 绝对不会并行调用闭包,因此可以安全传入`FnMut`类型的闭包
    pub async fn retry<F, R>(mut func: F) -> Result<(), String>
    where
        F: FnMut() -> R,
        R: Future<Output = Result<(), String>>,
    {
        for _ in 0..5 {
            if func().await.is_ok() {
                return Ok(());
            }
        }
        Err("重试5次均失败".into())
    }
    

    虽然注释不会直接影响编译,但这能帮你自己和其他使用者明确这个函数的串行语义,而且你自己用的时候也能更放心——毕竟编译器有时候只是需要你用代码逻辑给它“证明”安全性。

哦对了,还有个极端的下策:如果真遇到编译器死心眼不通过,你可以试试用RefCell做运行时的可变借用检查,但这属于迫不得已的选择,能在编译时解决的问题,咱尽量不拖到运行时~

备注:内容来源于stack exchange,提问作者Thomas

火山引擎 最新活动