#2340·reactive

AsyncSchedulerBase 的绝对调度无法应对时钟漂移

作者: idg10创建于 2026年9月4日更新于 2026年9月4日
标签[area] AsyncRx

背景 IAsyncScheduler 定义了一种方法,允许在未来的特定时间安排工作:

cs
ValueTask<IAsyncDisposable> ScheduleAsync(Func<CancellationToken, ValueTask> action, DateTimeOffset dueTime);

这是接口定义的三种重载方法之一。还有一种绝对时间方法,以及一个相对时间(TimeSpan)和一个即时方法。 AsyncSchedulerBase 只要求派生类实现一个即时方法:

cs
protected abstract ValueTask ScheduleAsyncCore(Func<CancellationToken, ValueTask> action, CancellationToken token);

它为您实现了两个基于时间版本,使用 async 方法进行 await,直到到期时间。为了使派生类能够定义时间流逝的方式,它们还实现了此方法:

cs
protected abstract ValueTask Delay(TimeSpan dueTime, CancellationToken token);

这对于虚拟时间来说是可以的,但对于实时来说是有问题的:它无法应对系统时间更改。如果系统时间被调整,为绝对时间安排的工作似乎会在错误的时间运行。要了解为什么,请查看 ScheduleAsyncDateTimeOffset dueTime 重载方法的实现:

cs
return ScheduleAsyncCore(async ct =>
{
    var dueTimeRelative = Normalize(dueTime - Now); // TODO: Support clock drift and clock changes.
    await Delay(dueTimeRelative, ct); // NB: Honor SynchronizationContext to stay on scheduler.
    await action(ct);
});

注释表明原作者意识到了这个缺点。问题在于它将绝对时间转换为 TimeSpan,这意味着如果系统时钟在安排此工作后发生变化,则不会考虑到此时间变化。这可能导致工作以错误的顺序处理。假设以下情况发生:

内容来源: dotnet/reactive