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

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::Pendingtokio 等待下一个事件,即唤醒你的未来或定时器。
  • 如果未来醒来,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();
}

Playground

正如您已经注意到的,这失败了 - unwrap_errOk 上调用时出现恐慌,并且超时 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.