So I wrote an asynchronous version of AutoResetEvent
:
public sealed class AutoResetEventAsync {
private readonly Task<bool> falseResult = Task.FromResult(false);
private readonly ConcurrentQueue<Handler> handlers = new ConcurrentQueue<Handler>();
private readonly Task<bool> trueResult = Task.FromResult(true);
private int isSet;
public AutoResetEventAsync(bool initialState) {
this.isSet = initialState ? 1 : 0;
}
public void Set() {
this.isSet = 1;
Handler handler;
// Notify first alive handler
while (this.handlers.TryDequeue(out handler))
if (handler.TryUnsubscribe()) {
handler.Invoke();
break;
}
}
public Task<bool> WaitAsync() {
return this.WaitAsync(CancellationToken.None);
}
public Task<bool> WaitAsync(TimeSpan timeout) {
if (timeout == Timeout.InfiniteTimeSpan)
return this.WaitAsync();
var tokenSource = new CancellationTokenSource(timeout);
return this.WaitAsync(tokenSource.Token);
}
public Task<bool> WaitAsync(CancellationToken cancellationToken) {
// Short path
if (this.TryReset())
return this.trueResult;
if (cancellationToken.IsCancellationRequested)
return this.falseResult;
// Wait for a signal
var completionSource = new TaskCompletionSource<bool>(false);
var handler = new Handler(() => {
if (this.TryReset())
completionSource.TrySetResult(true);
});
// Subscribe
this.handlers.Enqueue(handler);
if (this.TryReset()) {
// Unsubscribe
handler.TryUnsubscribe();
return this.trueResult;
}
cancellationToken.Register(() => {
// Try to unsubscribe, if success then the handler wasn't invoked so we could set false result,
// else the handler was invoked and the result is already set.
if (handler.TryUnsubscribe())
completionSource.TrySetResult(false);
});
return completionSource.Task;
}
private bool TryReset() {
return Interlocked.CompareExchange(ref this.isSet, 0, 1) == 1;
}
private sealed class Handler {
private readonly Action handler;
private int isAlive = 1;
public Handler(Action handler) {
this.handler = handler;
}
public void Invoke() {
this.handler();
}
public bool TryUnsubscribe() {
return Interlocked.CompareExchange(ref this.isAlive, 0, 1) == 1;
}
}
}
In my application it works well.
The question is: am I missing some concurrency issue?
I tested concurrent invocation of WaitAsync
method, so, maybe you could suggest a better test scenario?
WaitAsync()
supposed to be FIFO? \$\endgroup\$