为什么在系统进入睡眠或休眠时,Task.Delay没有按时唤醒?

后端开发 2026-07-12

我正尝试在C#中使用 Task.Delay 等待到某个特定时间。这基本上可以正常工作,但如果在我进入 Task.Delay 与系统唤醒之间,系统进入睡眠(S3/S4休眠),延迟会被睡眠时间拖长。

var now = DateTime.UtcNow;
var target = /* some future UTC date */;

await Task.Delay(target - now);

Task.Delay 是否没有把低功耗状态下花费的时间计算在内?

解决方案

Task.Delay 使用 System.Threading.Timer(默认情况下使用 TimeProvider 时)。System.Threading.Timer 使用 Environment.TickCount64SetThreadpoolTimer,并设置相对等待。

查看 关于 SetThreadpoolTimer 的文档 时,我们看到以下相关说明:

如果pftDueTime指定的到期时间是相对的,系统进入睡眠或休眠所花费的时间不计入计时器的到期。当系统处于唤醒状态时,累计经过的时间达到计时器的相对到期时间或周期时,计时器就会被触发。如果pftDueTime指定的到期时间是绝对的,系统进入睡眠或休眠所花费的时间会计入到期时间中。如果计时器在系统睡眠时到期,则在系统唤醒时立即触发计时器。

Environment.TickCount64 使用 QueryUnbiasedInterruptTime,其中 包含了类似的说明

因此,如果我们试图将延迟推到一个具体的实际时间,基本就没办法实现。理想情况下,使用一个实现了完善调度器的库。或者对于Windows的特定解决方案,可以监听 SystemEvents.PowerModeChanged

public static class WallClockDelay
{
    public static async Task DelayUntilUtcAsync(DateTime utcTarget, CancellationToken ct)
    {
        if (utcTarget.Kind != DateTimeKind.Utc)
        {
            throw new ArgumentException("utcTarget must be a UTC DateTime", nameof(utcTarget));
        }

        while (true)
        {
            ct.ThrowIfCancellationRequested();

            var remaining = utcTarget - DateTime.UtcNow;
            if (remaining <= TimeSpan.Zero)
            {
                return;
            }

            using var resumeInterrupt = new CancellationTokenSource();
            PowerModeChangedEventHandler onPowerModeChanged = (s, e) =>
            {
                if (e.Mode == PowerModes.Resume)
                {
                    resumeInterrupt.Cancel();
                }
            };

            SystemEvents.PowerModeChanged += onPowerModeChanged;
            try
            {
                using var linked = CancellationTokenSource.CreateLinkedTokenSource(ct, resumeInterrupt.Token);
                await Task.Delay(remaining, linked.Token);
                return;
            }
            catch (OperationCanceledException) when (!ct.IsCancellationRequested)
            {
                // Resume signal interrupted delay; recompute remaining wall-clock time.
            }
            finally
            {
                SystemEvents.PowerModeChanged -= onPowerModeChanged;
            }
        }
    }
}
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章