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:
- Register a delegate with
CancellationToken.Register
that will setAutoResetEvent
. - Using
TaskCompletionSource
. But, since I cannot reuseTaskCompletionSource
, I came up with the solution to queue newTaskCompletionSource
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.