1

我有一个简单的ITimer界面,它只有一个经典的Elapsed.NET 事件,该事件在某个时间跨度后引发。

interface ITimer
{
    void Start(TimeSpan interval);
    void Stop();
    public event EventHandler Elapsed;

}

现在我想创建一个类似于Task.Delay(TimeSpan)但它应该使用该ITimer抽象而不是“实时”时间的功能。Task.Delay(TimeSpan)尽管返回 aValueTask而不是 a可能是一个好主意,但形式也不同,Task因为它可以实现无分配并且通常可能更高性能。无论如何,延迟的完成也只需要等待一次。

现在一个有点幼稚的实现可能看起来像这样。这里的问题是,使用 a 根本没有任何好处ValueTask。一个实现看起来如何能够充分利用ValueTask承诺(便宜且无需分配)。

ValueTask Delay(TimeSpan duration, CancellationToken cancellationToken)
{
    // We get the ITimer instance from somwhere, doesn't matter for this question.
    ITimer timer = timerFactory.Create();
    var tcs = new TaskCompletionSource<bool>();
    timer.Elapsed += (_, __) => { tcs.SetResult(true); };
    cancellationToken.Register(() =>
    {
        timer.Stop();
        throw new OperationCanceledException();
    });
    timer.Start(duration);
    return new ValueTask(tcs.Task);
}
4

1 回答 1

2

上写的不多ManualResetValueTaskSourceCore<TResult>。它本质上是一个TaskCompletionSource<T>but for ValueTask<T>

它真的打算在你的计时器这样的类型中使用,但如果你想将它用作外部方法,你可以为它创建一个包装器:

public sealed class ManualResetValueTaskSource<T> : IValueTaskSource<T>, IValueTaskSource
{
  private ManualResetValueTaskSourceCore<T> _logic; // mutable struct; do not make this readonly

  public bool RunContinuationsAsynchronously
  {
    get => _logic.RunContinuationsAsynchronously;
    set => _logic.RunContinuationsAsynchronously = value;
  }
  public void Reset() => _logic.Reset();
  public void SetResult(T result) => _logic.SetResult(result);
  public void SetException(Exception error) => _logic.SetException(error);

  short IValueTaskSource.Version => _logic.Version;
  short IValueTaskSource<T>.Version => _logic.Version;
  void IValueTaskSource.GetResult(short token) => _logic.GetResult(token);
  T IValueTaskSource<T>.GetResult(short token) => _logic.GetResult(token);
  ValueTaskSourceStatus IValueTaskSource.GetStatus(short token) => _logic.GetStatus(token);
  ValueTaskSourceStatus IValueTaskSource<T>.GetStatus(short token) => _logic.GetStatus(token);

  void IValueTaskSource.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) => _logic.OnCompleted(continuation, state, token, flags);
  void IValueTaskSource<T>.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) => _logic.OnCompleted(continuation, state, token, flags);
}

然后您可以这样使用它(为简单起见删除了取消):

ValueTask Delay(TimeSpan duration)
{
  ITimer timer = timerFactory.Create();
  var vts = new ManualResetValueTaskSource<object>();
  TimerElapsed eventHandler = null;
  eventHandler = (_, __) =>
  {
    timer.Elapsed -= eventHandler;
    vts.SetResult(null);
  };
  timer.Elapsed += eventHandler;
  timer.Start(duration);
  return new ValueTask(vts);
}

请注意,由于这是一个外部方法,您仍将分配ManualResetValueTaskSource<T>(以及委托)。所以这并不能真正给你买任何东西,我会选择在TaskCompletionSource<T>这里使用。当您添加CancellationToken支持并且必须处理计时器触发 ( vts.SetResult) 和取消 ( vts.SetException) 之间的竞争条件时尤其如此,因为我很确定每个 ValueTask 操作应该只发生其中一个,并且没有Try*变体我们的ManualResetValueTaskSource<T>喜欢TaskCompletionSource<T>有。

ValueTask真的是为了成为一等公民;即,理想情况下,您将更新实际的计时器实现(和接口)以支持ValueTask Delay(TimeSpan)其自己的类而不是外部方法。在计时器实例内部,它可以重用ValueTask支持类型(ManualResetValueTaskSource<T>),它可以知道何时调用Reset,并且可以完全避免委托/事件。

于 2021-02-26T14:51:01.340 回答