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

如何在tokio::spawn中使用&self?异步服务器改造遇生命周期问题

Tokio异步服务器改造:生命周期与并发任务执行问题

编译错误

error[E0521]: borrowed data escapes outside of associated function
  --> src/server/mod.rs:52:24
   |
50 |     pub async fn run(&self) {
   |                      -----
   |                      |
   |                      `self` is a reference that is only valid in the associated function body
   |                      let's call the lifetime of this reference `'1`
51 |         info!("server started");
52 |         let arc_self = Arc::new(self);
   |                        ^^^^^^^^^^^^^^
   |                        |
   |                        `self` escapes the associated function body here
   |                        argument requires that `'1` must outlive `'static`

原始问题代码

pub async fn run(&self) {
    info!("server started");
    let arc_self = Arc::new(self);

    let listener_address = arc_self.settings.address.clone();
    let listener = TcpListener::bind(&listener_address)
        .await
        .expect("Failed to bind to the server address");

    tokio::spawn(async move {
        arc_self.listen(listener).await;
    });

    let arc_self_clone = arc_self.clone();
    tokio::spawn(async move {
        arc_self_clone.clean_cache().await;
    });
}

无效尝试

尝试1:顺序执行导致阻塞

pub async fn run(&self) {
    info!("server started");

    let listener_address = self.settings.address.clone();
    let listener = TcpListener::bind(&listener_address)
        .await
        .expect("Failed to bind to the server address");

    self.listen(listener).await; // 永久阻塞
    
    self.clean_cache().await; // 永远不会执行
}

问题:listen是处理连接的无限循环方法,会永久阻塞当前任务,导致后续clean_cache完全无法执行。

尝试2:使用Arc但任务未持续运行

pub async fn run(self: Arc<Self>) {
    info!("server started");
    let listener_address = self.clone().settings.address.clone();

    tokio::spawn({
        let me = Arc::clone(&self);
        eprintln!("yes");
        async move  {
            eprintln!("no");
            let listener = TcpListener::bind(&listener_address).await.unwrap();
            me.listen(listener).await;
        }
    });

    tokio::spawn({
        let me = Arc::clone(&self);
        async move {
            me.clean_cache().await;
        }
    });
}

问题:run函数执行完毕后直接返回,Tokio Runtime会判定整个任务结束,进而取消所有由它启动的后台spawn任务,导致两个任务都无法持续运行。

正确解决方案

核心解决逻辑:

  1. 让run方法接收Arc<Self>类型的self,确保实例生命周期满足'static(Tokio的spawn要求任务捕获的变量必须是'static)。
  2. 使用tokio::join!等待所有后台任务完成,避免run函数提前返回导致任务被取消。
pub async fn run(self: Arc<Self>) {
    info!("server started");
    let listener_address = self.settings.address.clone();

    // 启动监听任务
    let listen_task = tokio::spawn({
        let me = Arc::clone(&self);
        async move {
            let listener = TcpListener::bind(&listener_address)
                .await
                .expect("Failed to bind to server address");
            me.listen(listener).await;
        }
    });

    // 启动缓存清理任务
    let clean_cache_task = tokio::spawn({
        let me = Arc::clone(&self);
        async move {
            me.clean_cache().await;
        }
    });

    // 等待两个任务完成(若listen是无限循环,此处会一直阻塞)
    let _ = tokio::join!(listen_task, clean_cache_task);
}

额外说明

如果clean_cache需要定时重复执行(而非一次性任务),可以在方法内部实现循环+延迟逻辑:

async fn clean_cache(&self) {
    loop {
        // 执行缓存清理逻辑
        self.do_clean_cache().await;
        // 每隔30分钟执行一次
        tokio::time::sleep(tokio::time::Duration::from_secs(1800)).await;
    }
}

这样clean_cache_task会持续运行,和listen_task一起由tokio::join!等待,确保两个任务都能正常工作。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 18:58:40