为什么在系统进入睡眠或休眠时,Task.Delay没有按时唤醒?
我正尝试在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.TickCount64 和 SetThreadpoolTimer,并设置相对等待。
查看 关于 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导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。