异步任务间获取JoinHandle遇阻:Arc使用与编译错误排查
异步任务监控方案的问题与解决
问题背景
我需要实现一套逻辑:生成多个无限循环的异步任务,同时通过独立监控任务追踪这些任务的运行状态。最初方案是将每个任务的JoinHandle存入以字符串为键的HashMap,再让监控任务循环调用is_finished()检查状态。但遇到以下问题:
- 不克隆
HashMap无法传递给监控任务,又担心克隆会复制任务句柄导致无法引用原任务,于是尝试用Arc智能指针,但实现出现编译错误。
错误代码与报错信息
type TasksMap = HashMap<String, Arc<JoinHandle<()>>>; pub async fn monitor_each(db: &DbClient) -> Result<()> { let orderbooks = db::get_orderbooks().await?; for (address, _) in orderbooks { // do some stuff let ob_monitor_task = orderbook.clone().monitor(); // Add the OBs to a map of tasks, so we can monitor if they're running tasks .entry(orderbook.addr.to_string()) .or_insert(ob_monitor_task); } Ok(()) } /// Periodically check status of tasks async fn check_tasks_status(tasks: &TasksMap) -> Result<()> { tokio::spawn(async move { loop { for (ob_address, task) in tasks.iter() { let og_task = Arc::get_mut(task).unwrap(); // 编译错误位置 if og_task.is_finished() { error!("Monitor task not running for OB with address {ob_address}"); } } tokio::time::sleep(Duration::from_secs(10)).await; } }) .await? } impl Orderbook { /// Starts a task that monitors periodically the orderbook. pub fn monitor(&mut self) -> Arc<JoinHandle<()>> { let handle = tokio::spawn(async move { loop { // do some stuff tokio::time::sleep(Duration::from_secs(5)).await; } }); Arc::new(handle) } }
编译错误:
Note: expected mutable reference `&mut Arc<_>` found reference `&Arc<tokio::task::JoinHandle<()>>`
同时我不确定从Arc中获取的og_task是否为原任务的句柄。
解决方案
错误根源
Arc::get_mut要求传入可变引用(&mut Arc<T>),但tasks.iter()返回的是不可变引用(&Arc<JoinHandle<()>>),类型不匹配导致报错。- 完全没必要使用
Arc::get_mut:JoinHandle::is_finished()是不可变方法,直接通过Arc的不可变引用即可调用。
修正后的代码
调整监控任务函数
/// Periodically check status of tasks async fn check_tasks_status(tasks: Arc<TasksMap>) -> Result<()> { tokio::spawn(async move { loop { for (ob_address, task) in tasks.iter() { // 直接通过Arc的不可变引用调用is_finished,自动解引用到JoinHandle if task.is_finished() { error!("Monitor task not running for OB with address {ob_address}"); } } tokio::time::sleep(Duration::from_secs(10)).await; } }) .await? }
调整任务创建与Arc传递逻辑
pub async fn monitor_each(db: &DbClient) -> Result<()> { // 用Arc包裹HashMap,方便多任务共享 let mut tasks = Arc::new(HashMap::new()); let orderbooks = db::get_orderbooks().await?; for (address, _) in orderbooks { // 假设此处获取或初始化Orderbook实例 let mut orderbook = ...; let ob_monitor_task = orderbook.monitor(); // 使用Arc::make_mut获取内部HashMap的可变引用,仅当唯一引用时直接修改,否则克隆(初始化阶段不会触发克隆) let mut tasks_mut = Arc::make_mut(&mut tasks); tasks_mut .entry(orderbook.addr.to_string()) .or_insert(ob_monitor_task); } // 克隆Arc传递给监控任务,仅增加引用计数,不会复制任务句柄或HashMap check_tasks_status(tasks.clone()).await?; Ok(()) }
关键说明
- Arc的作用:克隆
Arc只会增加引用计数,不会复制内部的HashMap或JoinHandle,所有共享的Arc都指向同一个原任务句柄,监控任务能正确获取原任务状态。 - JoinHandle的访问:
Arc实现了Dereftrait,会自动解引用到内部的JoinHandle,因此可以直接通过task.is_finished()调用方法,无需额外转换。 - 修改共享HashMap:初始化阶段用
Arc::make_mut获取可变引用,确保安全修改共享的HashMap,后续监控任务仅做只读操作,无需可变权限。
内容的提问来源于stack exchange,提问作者Milkncookiez
相关产品推荐
相关产品推荐

