首页 > 解决方案 > 将 tokio::task::JoinHandle 存储在 HashMap 中并从另一个任务中访问它

问题描述

问候Rustaceans,

我目前正在尝试使用serenity-rs移植一个 JVM 应用程序(准确地说是一个不和谐的机器人)来生锈以获得乐趣和教育,而我目前正在用 tokio 任务和共享状态碰壁。我对整个生锈体验很陌生,所以请保持温和。

基本思想是,某些任务异步启动,并在等待一段时间后将一些数据插入共享(并发)映射中。程序的另一部分是监听事件,如果在第一个任务还在等待的时候发生了一个这样的事件,它将取消该任务,从而导致数据不会被插入到共享映射中。

我从一个“简化”示例开始,它看起来像这样,并且只依赖于 tokio。

[dependencies]
tokio = { version = "1", features = ["full"] }
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;

use tokio::join;
use tokio::sync::RwLock;
use tokio::time;

#[tokio::main]
async fn main() {
    let mut data: Arc<RwLock<HashMap<_,_>>> =
        Arc::new(RwLock::new(HashMap::new()));

    let mut tasks: Arc<RwLock<HashMap<String, _>>> =
        Arc::new(RwLock::new(HashMap::new()));

    let mut d1 = data.clone();
    let handle_a = tokio::spawn(async move {
        time::sleep(Duration::from_secs(30)).await;
        {
            let mut lock = d1.write().await;
            lock.insert("foo", "bar");
        }
    });

    let handle_a = Arc::new(handle_a);
    {
        let mut map = tasks.write().await;
        map.insert("the_task".to_string(), handle_a.clone());
    }

    let mut d2 = data.clone();
    let handle_b = tokio::spawn(async move {
        tokio::time::sleep(Duration::from_secs(10)).await;
        {
            let d = d2.read().await;
            let res = d.get("foo");
            println!("After 10sec: {}", res.unwrap_or(&"None"));
            let mut m = tasks.write().await;
            {
                let t = m.get("the_task").unwrap();
                println!("Now cancelling: {:?}", t);
                t.abort();
                let _ = m.remove("the_task");
            }
        }
        tokio::time::sleep(Duration::from_secs(25)).await;
        {
            let d = d2.read().await;
            let res = d.get("foo").unwrap_or(&"None");
            println!("After 35sec: {}", res)
        }
    });

    join!(handle_a, handle_b);
}

当前编译时出现以下错误:

55 |     join!(handle_a, handle_b);
   |           ^^^^^^^^ `Arc<tokio::task::JoinHandle<()>>` is not a future

我已经包装handle_a了一个,Arc因为我想在“主函数的末尾”等待它,同时仍然能够将对它的引用放入任务映射中。所以这显然似乎是错误的,但我想不出另一种方式来处理这个问题。简单地调用deref()handle_a给出一个不同的错误:

`Future` is implemented for `&mut tokio::task::JoinHandle<()>`, but not for `&tokio::task::JoinHandle<()>

我想这是有道理的,因为Arc状态的文档:

默认情况下,Rust 中的共享引用不允许突变,Arc 也不例外:您通常无法获得对 Arc 中某些内容的可变引用。

我认为 Rust 中期货的“基于拉取”的方法真正让我在这里挣扎,因为我需要一个对 JoinHandle 的引用以在 main 函数的末尾等待它。

可能这只是解决这个问题的完全错误的方法,所以我会非常感谢任何朝着正确方向的提示或轻推。

如果你读到这里,已经感谢你的时间了!

编辑:修复了接受的答案中提到的错字。

标签: rustasync-awaitrust-tokio

解决方案


tokio::spawn已经在后台执行未来。如果你想等待它的结果,你只需要等待它,在这种情况下你不需要。您可以简单地将其替换为b.await.

您在任务名称中还有一个小的逻辑错误 - 您使用"thetask"而不是"the_task"在一个查询中。我已经清理了一些代码,你可以在这个Playground上找到工作版本。


我不熟悉 Discord 机器人的设计,但如果你只有一个一直在运行的“事件循环”,负责创建和取消任务,你不需要为它创建一个任务tokio::spawn但可以直接在main函数中运行它。这意味着它能够tasks独占地图,并且您不会遇到共享所有权的问题。


推荐阅读