rust在语言上提供了异步抽象,但异步怎么跑是要依赖具体的运行时的。也因此,一个库想开发异步逻辑的时候(如scc异步container库),并不需要依赖tokio,只要库使用者使用tokio就行了。不过,很多库要使用tokio::spawn
来创建完全解耦的task,也因此可能不得不依赖tokio。
runtime
tokio的异步代码只能在tokio的运行时中进行。
runtime.enter()
会生成RAII的guard变量,变量生命周期期间程序处于对应runtime中。block_on会自动调用enter,在block_on调用完成后自动退出runtime。
fn main() -> Result<()> {
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()?;
runtime.block_on(async{
tokio::spawn(async {
tokio::time::sleep(Duration::from_secs(1)).await;
println!("Hello World");
}).await?;
})?;
Ok(())
}
fn main() -> Result<()> {
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()?;
let _enter_guard = runtime.enter();
let _ = tokio::spawn(async {
tokio::time::sleep(Duration::from_secs(1)).await;
println!("Hello World");
});
thread::sleep(Duration::from_secs(3));
Ok(())
}
两种thread
core thread: 一开始就会创建默认为CPU核心数量的core线程,即wocker线程,会获取任务进行调度。任务为Future类型。当任务执行到await(相当于yield)的时候,wocker可以进行任务切换。因此,如果一个任务一直阻塞(或长时间计算)跑不到await的话,它所在的wocker就会无法继续完成其他任务。
block thread: 按需创建,任务独占线程,由闭包生成。和普通的线程差不多意思。也因此可以执行阻塞任务而不影响其他任务。任务结束后默认存活十秒从而可以被复用。
普通线程生成函数thread::spawn
创建的线程与堵塞线程一样都是独占的,但是spawn_blocking
返回tokio::task::JoinHandle
类型实现了Future
,你可以在一个异步任务中等待它,而thread::spawn
做不到这点。
async
async函数调用后会返回Future,从而可以await。async函数会被分配到core thread来调度。
只有async函数才可以调用另一个async函数,因此具有传染性。在异步中调用同步代码可以用block thread解决,而在同步中调用异步代码则要新建tokio运行时:
async fn foo1() -> u32 {
10
}
fn foo() {
//使用new_current_thread占据当前线程,不另起炉灶
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build().unwrap();
let num = rt.block_on(foo1()); // 调用了此异步函数
// 或者像下面这样写
//let num = rt.block_on(async {
// foo1().await
//});
println!("{}", num);
}
await
- async函数或代码块需要被.await才会执行。
- tokio::spawn和tokio::task::spawn_blocking会立即开始执行,无论它们的JoinHandle是否被.await。(因此异步任务并不是完全连续的依赖树)
- 定时器和延时需要.await来响应它们的完成。
main宏
使用#[tokio::main]
即可让main函数自动进入tokio runtime:
#[tokio::main]
async fn main() {
println!("Hello world");
}
等价于:
fn main() {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
println!("Hello world");
})
}
使用#[tokio::test]
代替#[test]
即可让测试自动进入tokio runtime。
定制线程数
在builder里面可以用worker_threads
来指定worker数量。对new_current_thread
的builder无效。
let runtime = tokio::runtime::Builder::new_multi_thread()
.worker_threads(thread_num)
.enable_all()
.build().unwrap();
time
spawn内的panic
spawn内的panic不会中断程序,只会打印到stderr。但可以通过分析任务结束后返回的JoinError,若错误原因是panic,就可以通过std::panic::resume_unwind
让panic生效,以中断程序。
while let Some(res) = join_set.join_next().await {
if let Err(e) = res {
if e.is_panic() {
std::panic::resume_unwind(e.into_panic());
}
}
}