正文
假如说有这样一个异步函数 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 论坛中看到了相关问题。解决方案是使用 Arc
或 Arc/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
而不是捕获它的引用(捕获引用是默认行为)。
参考
- 理解tokio核心(2): task:https://rust-book.junmajinlong.com/ch100/02_understand_tokio_task.html
- Rust 论坛 How to use self while spawning a thread from method:https://users.rust-lang.org/t/how-to-use-self-while-spawning-a-thread-from-method/8282