在学习Rust tokio多线程时发现一个现象,不知道为什么。
这是代码。
use std::{thread, time::Duration};
use chrono::Local;
fn now() -> String {
Local::now().format("%F %T").to_string()
}
async fn async_task(i: u64) {
thread::sleep(Duration::from_secs(i));
println!("{}.async task {}!", now(), i);
}
#[tokio::main]
async fn main() {
for i in 0..10 {
tokio::spawn(async_task(i));
}
println!("{}.main thread", now());
}
我运行代码,发现每10个异步任务都被执行了。结果如下
2023-09-05 22:08:05.async task 0!
2023-09-05 22:08:05.main thread
2023-09-05 22:08:06.async task 1!
2023-09-05 22:08:07.async task 2!
2023-09-05 22:08:08.async task 3!
2023-09-05 22:08:09.async task 4!
2023-09-05 22:08:10.async task 5!
2023-09-05 22:08:11.async task 6!
2023-09-05 22:08:12.async task 7!
2023-09-05 22:08:13.async task 8!
2023-09-05 22:08:14.async task 9!
当我注释掉线程println
中的代码时main
,只有少数任务会被执行。
#[tokio::main]
async fn main() {
for i in 0..10 {
tokio::spawn(async_task(i));
}
// println!("{}.main thread", now());
}
结果如下
2023-09-05 22:10:51.async task 0!
2023-09-05 22:10:52.async task 1!
我已经尝试过很多次了,每次都会出现这种差异。每次尝试时,未注释掉的代码println
都会执行所有异步任务,而另一个则不会。
我真的不明白为什么aprintln
可以产生如此大的影响。如果有人能提供帮助,我将不胜感激。
您的代码行为怪异的真正原因是因为您使用
std::thread::sleep
而不是tokio::time::sleep
.使用永远不会阻塞的异步函数时,这一点很重要。异步反应器是非抢占式的,这意味着它们只能在任务之间进行调度
.await
。这意味着,如果您这样做std::thread::sleep
,您就会阻止整个程序。这也是为什么在您的工作程序中,输出并不总是以正确的顺序打印,尽管每次打印之间的时间应该是一整秒。如果您替换
std::thread::sleep
为tokio::time::sleep
,您将获得一致的行为(尽管这可能不是您想要的行为):为什么这样?因为
main
完成后,tokio 会取消所有剩余任务。但同样,tokio 只能在.await
点上进行安排,因此在您的“工作”示例中,tokio 仍然尝试取消任务,但没有成功,因为它们从未达到某个.await
点。所以看起来东京正在等待他们,但事实并非如此。解决这个问题最简单的方法就是不让
main
函数结束。(实际上这是我想到的唯一方法)有多种方法可以实现这一目标。就您的情况而言,在我看来您的意图是等待所有子任务完成,然后结束程序。这可以通过等待连接句柄来完成:
也就是说,一一等待它们有点乏味,而且也可以防止立即传播错误。相反,您可以使用
futures::future::try_join_all
: