为什么任务超时时不恐慌?
why not panic when task timeout?
我设置超时为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
孕育你的未来。如果它returns Poll::Ready
,定时器被丢弃,未来成功。如果它 returns Poll::Pending
,tokio
等待下一个事件,即唤醒你的未来或定时器。
- 如果未来醒来,
tokio
再次轮询它。如果它 returns Poll::Ready
- 再次,计时器被丢弃,未来成功。
- 如果定时器唤醒,
tokio
最后一次轮询未来;如果它仍然是 Poll::Pending
,它超时并且不再被轮询,并且 timeout
return 是一个错误。
然而,在你的情况下,未来做 not return Poll::Pending
- 它在 thread::sleep
内阻塞。因此,即使计时器可能在一秒过去后触发,tokio
也没有办法做出反应 - 它等待未来到 return,未来 returns 只有在线程被解除阻塞之后,并且,由于块内没有 await
,它 returns Poll::Ready
- 所以甚至没有检查计时器。
要解决此问题,您需要对异步代码中的任何暂停使用 tokio::time::sleep
。有了它,未来就会正常超时。为了说明这一说法,让我们看一下与您的原始代码等效的 self-contained 示例:
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
在 Ok
上调用时出现恐慌,并且超时 returns Ok
因为未来没有计时正确输出。
但是当用 tokio::time::sleep(...).await
替换 std::thread::sleep(...)
时...
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.
我设置超时为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
孕育你的未来。如果它returnsPoll::Ready
,定时器被丢弃,未来成功。如果它 returnsPoll::Pending
,tokio
等待下一个事件,即唤醒你的未来或定时器。- 如果未来醒来,
tokio
再次轮询它。如果它 returnsPoll::Ready
- 再次,计时器被丢弃,未来成功。 - 如果定时器唤醒,
tokio
最后一次轮询未来;如果它仍然是Poll::Pending
,它超时并且不再被轮询,并且timeout
return 是一个错误。
然而,在你的情况下,未来做 not return Poll::Pending
- 它在 thread::sleep
内阻塞。因此,即使计时器可能在一秒过去后触发,tokio
也没有办法做出反应 - 它等待未来到 return,未来 returns 只有在线程被解除阻塞之后,并且,由于块内没有 await
,它 returns Poll::Ready
- 所以甚至没有检查计时器。
要解决此问题,您需要对异步代码中的任何暂停使用 tokio::time::sleep
。有了它,未来就会正常超时。为了说明这一说法,让我们看一下与您的原始代码等效的 self-contained 示例:
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
在 Ok
上调用时出现恐慌,并且超时 returns Ok
因为未来没有计时正确输出。
但是当用 tokio::time::sleep(...).await
替换 std::thread::sleep(...)
时...
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.