我设置超时为1s,但是任务执行到3s,却没有发生panic。
#代码
#[should_panic]
fn test_timeout() {
let rt = create_runtime();
let timeout_duration = StdDuration::from_secs(1);
let sleep_duration = StdDuration::from_secs(3);
let _guard = rt.enter();
let timeout = time::timeout(timeout_duration, async {
log("timeout running");
thread::sleep(sleep_duration);
log("timeout finsihed");
"Ding!".to_string()
});
rt.block_on(timeout).unwrap();
}
最佳答案
使用thread::sleep
在异步代码中is almost always wrong .
从概念上讲,超时的工作原理如下:
-
tokio
生成一个计时器,该计时器将在指定的持续时间后唤醒。 -
tokio
孕育你的 future 。如果返回Poll::Ready
,计时器被扔掉, future 成功。如果返回Poll::Pending
,tokio
等待下一个事件,即唤醒您的 future 或计时器。 - 如果 future 醒来,
tokio
再次进行民意调查。如果返回Poll::Ready
- 再次,计时器被扔掉, future 成功。 - 如果定时器唤醒,
tokio
最后一次对 future 进行投票;如果还是Poll::Pending
,它超时并且不再轮询,并且timeout
返回错误。
但是,就您而言, future 不会返回 Poll::Pending
- 它阻塞在 thread::sleep
内。因此,即使计时器可能在一秒过去后触发,tokio
无法使用react - 它等待 future 返回,future 仅在线程解除阻塞后返回,并且,因为没有 await
在 block 内,它返回 Poll::Ready
- 所以计时器甚至没有被检查。
要解决此问题,您需要使用 tokio::time::sleep
对于异步代码内的任何暂停。有了它, future 就可以正确地超时。为了说明这一主张,让我们看一下与原始代码等效的独立示例:
use core::time::Duration;
use tokio::time::timeout;
#[tokio::main]
async fn main() {
let timeout_duration = Duration::from_secs(1);
let sleep_duration = Duration::from_secs(3);
timeout(timeout_duration, async {
println!("timeout running");
std::thread::sleep(sleep_duration);
println!("timeout finsihed");
"Ding!".to_string()
})
.await
.unwrap_err();
}
正如您已经注意到的,这失败了 - unwrap_err
调用时出现 panic Ok
,超时返回 Ok
因为 future 没有正确超时。
但是当替换 std::thread::sleep(...)
时与 tokio::time::sleep(...).await
...
use core::time::Duration;
use tokio::time::timeout;
#[tokio::main]
async fn main() {
let timeout_duration = Duration::from_secs(1);
let sleep_duration = Duration::from_secs(3);
timeout(timeout_duration, async {
println!("timeout running");
tokio::time::sleep(sleep_duration).await;
println!("timeout finsihed");
"Ding!".to_string()
})
.await
.unwrap_err();
}
...我们得到了预期的行为 - playground .
关于rust - 为什么任务超时时不 panic ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71537671/