如何等待 AutoResetEvent 并能够在调用取消后终止等待?

the*_*ist 1 c# asynchronous async-await

我需要能够等待事件,而且还能够在调用取消后终止等待。

我用AutoResetEvent它来等待信号以继续工作。为了能够取消等待,我想出了两个解决方案:

  1. CancellationToken.Register向will set注册一个委托AutoResetEvent
  2. 使用TaskCompletionSource。但是,由于我无法重用TaskCompletionSource,因此我想出了TaskCompletionSource每次触发新事件时对新事件进行排队的解决方案。

这些是正确的解决方案还是有更优雅的方法来做到这一点?

解决方案1

class MyClass
{
   AutoResetEvent _dataArrivedSignal = new AutoResetEvent (false);
   public Task RunAsync(CancellationToken cancellationToken)
   {
       return Task.Factory.StartNew(() =>
       {
          cancellationToken.Register(() => _dataArrivedSignal.Set());

          while(condition)
          {
              DoSomeWork();
              
              _dataArrivedSignal.WaitOne();

              cancellationToken.ThrowIfCancellationRequested();
          }
       }
   }

   private void OnDataArrived(EventArgs args)
   {
       _dataArrivedSignal.Set();
   }
}
Run Code Online (Sandbox Code Playgroud)

解决方案2

class MyClass
{
   ConcurrentQueue<TaskCompletionSource> _awaiters = new ConcurrentQueue<TaskCompletionSource>();
   TaskCompletionSource _waiter;

   public MyClass3()
   { 
       _waiter = new TaskCompletionSource();
       _awaiters.Enqueue(_waiter);
   }

   public Task RunAsync(CancellationToken cancellationToken)
   {
       return Task.Factory.StartNew(() =>
       {
          while(condition)
          {
              DoSomeWork();
              
              _awaiters.TryDequeue(out TaskCompletionSource waiter);
              waiter.Task.Wait(cancellationToken);

              cancellationToken.ThrowIfCancellationRequested();
          }
       }
   }

   private void OnDataArrived(EventArgs args)
   {
        var newWaiter = new TaskCompletionSource();
        _awaiters.Enqueue(newWaiter);

        _waiter.SetResult();
        _waiter = newWaiter;
   }
}
Run Code Online (Sandbox Code Playgroud)

Evk*_*Evk 5

是的,还有更优雅的方式。请注意,AutoResetEvent继承自WaitHandle. CancellationToken依次具有属性WaitHandle,描述为:

获取在取消令牌时发出信号的 WaitHandle。

然后,WaitHandle有一个静态方法WaitAny,它接受等待句柄数组并返回收到信号的第一个等待句柄数组中的索引。

因此,为了实现你想要的 - 使用:

public Task RunAsync(CancellationToken cancellationToken) {
    return Task.Factory.StartNew(() => {
        while (condition) {
            DoSomeWork();
            int signaled = WaitHandle.WaitAny(new[] { _dataArrivedSignal, cancellationToken.WaitHandle });
            if (signaled == 0) {
                // your _dataArrivedSignal was signaled
            }
            else {
                // cancellation token was signaled
            }
        }
    });
}
Run Code Online (Sandbox Code Playgroud)

WaitAny如果您WaitOne在实际代码中实际使用超时,也可以接受超时。