一、库引入
为了学习tokio库的全部特性,cargo.toml的配置如下:
[dependencies]
tokio = { version = "0.3", features = ["full"] }
二、Task使用示例
1.最简单的示例,验证tokio库导入成功,并能够成功运行。
#[tokio::main] //此处引入tokio 宏/macro
async fn main() {
println!("Hello, world!");
}
2.tokio任务阻塞+await示例
use std::thread::sleep;
use tokio::time::Duration;
#[tokio::main] //此处引入tokio 宏/macro
async fn main() {
//主线程
tokio::task::spawn_blocking(|| {
//运行在一个阻塞的线程,可以看作是一个比较耗时的操作
sleep(Duration::from_millis(10000));
println!("hi");
}).await.unwrap();//使用await关键字等待阻塞线程的任务完成
//要等待阻塞线程完成后,主线程才能执行
println!("hello");
}
这个示例执行的结果是,等待10s后先输出hi,后输出hello。
3.tokio任务阻塞示例
use std::thread::sleep;
use tokio::time::Duration;
#[tokio::main] //此处引入tokio 宏/macro
async fn main() {
//主线程
tokio::task::spawn_blocking(|| {
//运行在一个阻塞的线程,可以看作是一个比较耗时的操作
sleep(Duration::from_millis(10000));
println!("hi");
});//此处未使用await关键字等待阻塞线程的任务完成
//无需等待阻塞线程完成,主线程直接执行
println!("hello");
}
这个示例执行的结果是,先输出hello,然后等待10s输出hi
4.tokio任务不阻塞+await示例
use std::thread::sleep;
use tokio::time::Duration;
#[tokio::main] //此处引入tokio 宏/macro
async fn main() {
//主线程
//tokio::task::spawn等价于thread::spawn的异步使用
tokio::task::spawn(async{
//运行在一个不阻塞的线程
sleep(Duration::from_millis(10000));
println!("hi");
}).await.unwrap();//使用await关键字等待阻塞线程的任务完成
//要等待线程完成后,主线程才能执行
println!("hello");
}
这个示例执行的结果是,等待10s后先输出hi,后输出hello。
5.tokio任务不阻塞示例
use std::thread::sleep;
use tokio::time::Duration;
#[tokio::main] //此处引入tokio 宏/macro
async fn main() {
//主线程
//tokio::task::spawn等价于thread::spawn的异步使用
tokio::task::spawn(async{
//运行在一个不阻塞的线程
sleep(Duration::from_millis(10000));
println!("hi");
});//此处未使用await关键字等待阻塞线程的任务完成
//无需等待阻塞线程完成,主线程直接执行
println!("hello");
}
这个示例执行的结果是,先输出hello,然后等待10s输出hi
6.tokio多任务不阻塞示例
use std::thread::sleep;
#[tokio::main] //此处引入tokio 宏/macro
async fn main() {
tokio::task::spawn(async {//任务先抢占到线程就先输出hi1,操作耗时多,最后输出
println!("hi1");
});
tokio::task::spawn(async {//任务先抢占到线程就先输出hi2,操作耗时多,最后输出
println!("hi2");
});
println!("hello");
}
这个示例执行的结果是,先输出hello,后输出hi1和hi2,hi1和h2的出现顺序是不确定的。
7.tokio多任务不阻塞+单await示例
#[tokio::main] //此处引入tokio 宏/macro
async fn main() {
tokio::task::spawn(async {
println!("hi1");//一定最先输出
}).await;
tokio::task::spawn(async {
println!("hi2");//任务所属线程可能抢占不到执行所需的资源,主线程直接退出
});
println!("hello");//切到主线程,第二输出
}
这个示例执行的结果是,先等待h1输出,而后输出hello,最后输出hi2,也可能不输出hi2.
#[tokio::main] //此处引入tokio 宏/macro
async fn main() {
tokio::task::spawn(async {
println!("hi1");//任务所属线程优先级较高,可以抢占到执行所需的资源
});
tokio::task::spawn(async {
println!("hi2");//一定会执行,使用await会等待任务所属线程先完成,然后执行后续的操作
}).await;
println!("hello");//主线程最后执行
}
这个示例执行的结果是,可能先输出hi2,再输出hi1,最后输出hello,也可能是先输出hi1,再输出hi2,最后输出hello(这种可能性高)
8.tokio多任务阻塞示例
#[tokio::main] //此处引入tokio 宏/macro
async fn main() {
tokio::task::spawn_blocking(|| {
//运行在一个阻塞的线程中
println!("hi1");//分配到专属线程池执行
});
tokio::task::spawn_blocking(|| {
//运行在一个阻塞的线程中
println!("hi2");//分配到专属线程池执行
});
println!("hello");//因为任务处于阻塞,主线程最先输出
}
这个示例执行的结果是,先输出hello,再输出hi1和hi2,h1和h2输出顺序不定,为什么主线程在输出后不直接退出?因为还有任务处于阻塞没有返回,只有阻塞的任务完成,程序才会退出。
9.tokio多任务阻塞+单await示例
#[tokio::main] //此处引入tokio 宏/macro
async fn main() {
tokio::task::spawn_blocking(|| {
println!("hi1");//使用await关键字,主线程需要等待任务执行完成才能执行
}).await;
tokio::task::spawn_blocking(|| {
println!("hi2");//处于阻塞线程,等待主线程执行完,再执行
});
println!("hello");
}
这个示例执行的结果是,先输出hi1,然后输出hello,最后输出hi2
#[tokio::main] //此处引入tokio 宏/macro
async fn main() {
tokio::task::spawn_blocking(|| {
println!("hi1");//操作不耗时,直接执行,不能超过await执行的时间,超过最后执行
});
tokio::task::spawn_blocking(|| {
println!("hi2");//使用await关键字,主线程需要等待任务执行完成才能执行
}).await;
println!("hello");
}
这个示例执行的结果是,先输出hi1,然后输出hi2,最后输出hello
10.单线程环境下任务阻塞和任务不阻塞示例
use tokio::runtime::{Builder};
use std::thread::sleep;
use tokio::time::Duration;
fn main() {
//构造单线程tokio运行环境
let runtime = Builder::new_multi_thread().
max_threads(1).
enable_all().
build().
expect("create tokio runtime failed");
runtime.spawn(async {//相当于tokio::task::spawn
//处于单线程中
println!("hi1");
});
runtime.spawn(async {//相当于tokio::task::spawn
println!("hi2");//处于单线程中
});
println!("hello");
}
这个示例执行的结果是,先输出hello,再输出hi1,再输出hi2,这个输出顺序是确定的,和示例6有所区别
use tokio::runtime::{Builder};
use std::thread::sleep;
use tokio::time::Duration;
fn main() {
//构造单线程tokio运行环境
let runtime = Builder::new_multi_thread().
max_threads(1).
enable_all().
build().
expect("create tokio runtime failed");
runtime.spawn(async {//相当于tokio::task::spawn
//处于单线程中,执行了耗时的操作,影响了其他任务的执行
sleep(Duration::from_secs(10));
println!("hi1");
});
runtime.spawn(async {//相当于tokio::task::spawn
println!("hi2");//处于单线程中
});
println!("hello");
}
这个示例执行的结果是,先输出hello,等待10s,之后输出hi1,再输出hi2
use tokio::runtime::{Builder};
use std::thread::sleep;
use tokio::time::Duration;
fn main() {
//构造单线程tokio运行环境
let runtime = Builder::new_multi_thread().
max_threads(1).
enable_all().
build().
expect("create tokio runtime failed");
runtime.spawn_blocking(|| {//相当于tokio::task::spawn_blocking
//处于专属线程池中,执行耗时的操作不影响其他任务执行
sleep(Duration::from_secs(10));
println!("hi1");
});
runtime.spawn(async {//相当于tokio::task::spawn
println!("hi2");//处于单线程中
});
println!("hello");
}
这个示例执行的结果是,先输出hello,之后输出hi2,等待10s,再输出hi2
总结:可以看出tokio任务阻塞和任务不阻塞的功能大致相同。它们的区别是:tokio::task::spawn开启的任务不应执行可能阻塞的操作,这个任务如果阻塞,这个任务运行时的线程就会被阻塞,在这个线程下的其它任务也会被影响到,直到阻塞消除。tokio::task::spawn_blocking开启的任务可以执行阻塞操作,开启的任务运行在一个专属的线程池中,这个任务如果阻塞,不会影响到其它任务的完成。
11.block_in_place使用示例
#[tokio::main]
async fn main() {
tokio::task::block_in_place(|| {
println!("hi1");
});
tokio::task::block_in_place(|| {
println!("hi2");
});
println!("hello")
}
这个示例的执行结果是:先输出hi1,再输出hi2,最后输出hello
block_in_place作用和spawn_blocking作用类似,不同之处是,前者阻塞当前线程,等待任务完成后,再将当前线程中的其他任务中转移到另外一个线程执行,后者是将当前任务放到专属的线程池中,线程池选择一个线程阻塞等待任务完成,而当前的线程不会被阻塞。
12.yield_now使用示例
#[tokio::main]
async fn main() {
tokio::task::spawn(async {//任务时间不能超过await任务的时间,超过最后执行
println!("hi1");//任务所属线程优先级较高,可以抢占到执行所需的资源
});
tokio::task::spawn(async {
tokio::task::yield_now().await;//此处使用让出时间片,等待其他任务完成
println!("hi2");//一定会执行,使用await会等待任务所属线程先完成,然后执行后续的操作
}).await.unwrap();
println!("hello")
}
这个示例的执行结果:先输出hi1,再输出hi2 ,最后输出hello