You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在tokio_cron_scheduler的多个new_repeated_async任务中复用同一值的问题

解决Rust中tokio_cron_scheduler任务捕获变量的所有权冲突问题

问题重现

以下代码尝试使用tokio_cron_scheduler创建重复执行的异步任务,共享同一个Obj实例,但编译时触发错误:cannot move out of obj, a captured variable in an FnMut closure

use std::time::Duration;
use tokio_cron_scheduler::{JobScheduler, Job};

#[derive(Clone, Debug)]
struct Obj {
    a: String,
    b: u64,
}

#[tokio::main]
async fn main() {
    let obj = Obj {
        a: "A".to_string(),
        b: 64,
    };

    let sched = JobScheduler::new().await.unwrap();

    let job = Job::new_repeated_async(Duration::from_secs(2), |_, _| Box::pin(async move {
        foo(&obj);
    })).unwrap();
    sched.add(job).await.unwrap();

    let job = Job::new_repeated_async(Duration::from_secs(5), |_, _| Box::pin(async move {
        bar(&obj);
    })).unwrap();
    sched.add(job).await.unwrap();

    sched.start().await.unwrap();

    tokio::time::sleep(core::time::Duration::from_secs(100)).await;
}

fn foo(o: &Obj) {
    println!("{}", o.a);
}

fn bar(o: &Obj) {
    println!("{}", o.b);
}

错误原因

Job::new_repeated_async要求传入的闭包实现FnMut trait,因为任务会被重复执行。但代码中async move闭包会尝试捕获obj的所有权,第一次执行任务时,obj会被移出外层闭包,导致后续任务执行时外层闭包无法再访问该变量,触发所有权冲突。

解决方案

使用Arc(原子引用计数)实现线程安全的共享所有权,为每个任务单独克隆Arc实例,确保每个异步任务持有独立的引用副本,避免所有权移动冲突。

修正后的完整代码

use std::sync::Arc;
use std::time::Duration;
use tokio_cron_scheduler::{JobScheduler, Job};

#[derive(Clone, Debug)]
struct Obj {
    a: String,
    b: u64,
}

#[tokio::main]
async fn main() {
    // 用Arc包裹Obj,实现多任务间的线程安全共享
    let obj = Arc::new(Obj {
        a: "A".to_string(),
        b: 64,
    });

    let sched = JobScheduler::new().await.unwrap();

    // 为第一个任务克隆独立的Arc实例
    let obj_foo = obj.clone();
    let job = Job::new_repeated_async(Duration::from_secs(2), move |_, _| {
        Box::pin(async move {
            foo(&obj_foo);
        })
    }).unwrap();
    sched.add(job).await.unwrap();

    // 为第二个任务克隆独立的Arc实例
    let obj_bar = obj.clone();
    let job = Job::new_repeated_async(Duration::from_secs(5), move |_, _| {
        Box::pin(async move {
            bar(&obj_bar);
        })
    }).unwrap();
    sched.add(job).await.unwrap();

    sched.start().await.unwrap();

    tokio::time::sleep(core::time::Duration::from_secs(100)).await;
}

fn foo(o: &Arc<Obj>) {
    println!("{}", o.a);
}

fn bar(o: &Arc<Obj>) {
    println!("{}", o.b);
}

关键说明

  1. Arc的作用:Arc提供线程安全的引用计数,允许多个任务同时持有同一个Obj实例的引用,适配tokio多线程运行时的场景。
  2. 独立克隆Arc:每个任务必须单独克隆Arc,确保外层FnMut闭包每次执行时,异步任务都能持有自己的Arc副本,避免所有权被一次性移走。
  3. 外层闭包使用move:外层闭包通过move关键字将克隆后的Arc移入闭包,保证异步任务能稳定持有引用,不会出现变量生命周期问题。

内容的提问来源于stack exchange,提问作者Matthew Davis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 15:10:39