LOADING

加载过慢请开启缓存 浏览器默认开启

Rust 笔记(1)—— 关于 tokio spawn

正文

假如说有这样一个异步函数 async_func,如果在同步函数中不用 await 调用它,它是不会被执行的,除非使用 tokio::task::spawn 函数开启一个异步任务。该函数接受一个 Future 参数,会返回一个 tokio::task::JoinHandle<T>,其中范型 T 是异步任务的返回值。

JoinHandle类型可以通过await来等待异步任务的完成,也可以通过abort()来中断异步任务,异步任务被中断后返回JoinError类型。

举个例子:

async fn async_func() -> Result<(), Box<dyn std::error::Error>> {
    ...
    Ok(())
}

fn sync_func() {
    // handle 的类型为 JoinHandle<Result<(), Box<dyn std::error::Error>>>
    let handle = spawn(async_func());
    // 中断
    handle.abort();
    // 等待完成(需要在异步函数中)
    // handle.await
}

可以使用 tokio::join! 宏来等待多个 JoinHandle 执行完成。

这里的 spawn 如果不等待的话,就会自己独立执行,所以会要求引用数据的生命周期是 'static,也就是活到程序结束(因为异步任务也可能执行到程序结束)。这就会带来一些编码上的困难,例如下面的例子:

struct Manager;
impl Manager {
    async fn do_something_async(&self) {}

    fn start(&self) {
        spawn(self.do_something_async());
    }
}

上述的代码会报错:

error[E0521]: borrowed data escapes outside of method
  --> src/test.rs:22:15
   |
21 |     fn start(&self) {
   |              -----
   |              |
   |              `self` is a reference that is only valid in the method body
   |              let's call the lifetime of this reference `'1`
22 |         spawn(self.do_something_async());
   |               ^^^^^^^^^^^^^^^^^^^^^^^^^
   |               |
   |               `self` escapes the method body here
   |               argument requires that `'1` must outlive `'static`

spawn 要求 &self 的生命周期必须是 'static,我查阅了一些资料,从 Rust 论坛中看到了相关问题。解决方案是使用 ArcArc/Mutex(如果有可变引用)。

struct Manager {
    inner: Arc<ManagerInner>,
}
struct ManagerInner;
impl ManagerInner {
    async fn do_something_async(&self) {}
}

impl Manager {
    fn start(&self) {
        let inner_cloned = self.inner.clone();
        spawn(async move {
            inner_cloned.do_something_async().await;
        });
    }
}

通过这一层包裹,就不报错了。Arc 保证了引用可以活到程序结束(只要还有引用,就不会被回收),即使 Manager 被回收,只要异步任务还在进行,ManagerInner 还是存在一份。这边必须使用 move 告诉编译器移动 inner_cloned 而不是捕获它的引用(捕获引用是默认行为)。

参考