为什么任务超时时不恐慌?

los*_*sof 2 rust rust-tokio

我设置超时为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();
    }

Run Code Online (Sandbox Code Playgroud)

Cer*_*rus 5

thread::sleep在异步代码中使用几乎总是错误的

从概念上讲,超时的工作原理如下:

  • tokio产生一个计时器,该计时器将在指定的持续时间后唤醒。
  • tokio孕育你的未来。如果它返回Poll::Ready,则计时器被丢弃,未来成功。如果它返回Poll::Pendingtokio则等待下一个事件,即唤醒您的未来或计时器。
  • 如果 future 醒来,则tokio再次轮询它。如果它再次返回Poll::Ready,计时器将被丢弃,未来成功。
  • 如果定时器被唤醒,tokio则最后一次轮询 future;如果仍然是Poll::Pending,则超时并且不再轮询,并timeout返回错误。

然而,就您而言, future 不会返回-Poll::Pending它会阻塞在thread::sleep. 因此,即使计时器可以在一秒过去后触发,tokio也无法做出反应 - 它等待 future 返回,future 仅在线程解除阻塞后才返回,并且,由于await块内没有任何内容,因此它返回Poll::Ready- 所以计时器甚至没有被检查。

要解决此问题,您应该使用tokio::time::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");
        std::thread::sleep(sleep_duration);
        println!("timeout finsihed");
        "Ding!".to_string()
    })
    .await
    .unwrap_err();
}
Run Code Online (Sandbox Code Playgroud)

操场

正如您已经注意到的,这会失败 -unwrap_err调用时会出现恐慌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();
}
Run Code Online (Sandbox Code Playgroud)

...我们得到了预期的行为 - Playground