首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >等待具有.NET的单个ValueTask事件

等待具有.NET的单个ValueTask事件
EN

Stack Overflow用户
提问于 2021-02-26 14:04:16
回答 1查看 658关注 0票数 1

我有一个简单的ITimer接口,它只有一个经典的Elapsed .NET事件,这个事件是在某个时间段之后引发的。

代码语言:javascript
复制
interface ITimer
{
    void Start(TimeSpan interval);
    void Stop();
    public event EventHandler Elapsed;

}

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

现在,一个有点幼稚的实现可能会像这样。这里的问题是,根本没有使用ValueTask的好处。一个像这样的实现怎么会充分受益于ValueTask的承诺(廉价和免费分配)。

代码语言:javascript
复制
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);
}
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-02-26 14:51:01

ManualResetValueTaskSourceCore<TResult>上写的不多。它本质上是一个TaskCompletionSource<T>,但是对于ValueTask<T>来说。

它实际上是打算在诸如计时器这样的类型中使用,但是如果您想要将它用作外部方法,则可以使用为它创建一个包装器

代码语言:javascript
复制
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);
}

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

代码语言:javascript
复制
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操作中只有一个是这样的,而且在我们的ManualResetValueTaskSource<T>中没有像TaskCompletionSource<T>那样的Try*变体。

ValueTask实际上更像是一个一流的公民;也就是说,理想情况下,您应该更新实际的计时器实现(和接口),以便在自己的类中而不是在外部方法中支持ValueTask Delay(TimeSpan)。在计时器实例中,它可以重用ValueTask支持类型(ManualResetValueTaskSource<T>),它可以知道何时调用Reset,并且可以完全避免委托/事件。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/66387225

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档