Home > other >  How to wait on AutoResetEvent and be able to terminate the wait after cancel is called?
How to wait on AutoResetEvent and be able to terminate the wait after cancel is called?

Time:10-31

I need to be able to wait for an event, but also be able to terminate the wait after cancel is called.

I use AutoResetEvent as a way to wait on a signal to continue the work. To be able to cancel the wait, I came up with two solutions:

  1. Register a delegate with CancellationToken.Register that will set AutoResetEvent.
  2. Using TaskCompletionSource. But, since I cannot reuse TaskCompletionSource, I came up with the solution to queue new TaskCompletionSource each time a new event is fired.

Are these proper solutions or there are more elegant ways to do this?

Solution 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();
   }
}

Solution 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;
   }
}

CodePudding user response:

Yes, there is more elegant way. Note that AutoResetEvent inherits from WaitHandle. CancellationToken in turn has property WaitHandle, described as:

Gets a WaitHandle that is signaled when the token is canceled.

Then, WaitHandle has static method WaitAny which accepts array of wait handles and returns an index in that array of first wait handle that was signaled.

So to achieve what you want - use:

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
            }
        }
    });
}

WaitAny can also accept timeout in case you actually use WaitOne with timeout in real code.

  • Related