How to Await Events in C#

Is it possible to await an event instead of another async method?

You can use an instance of the SemaphoreSlim Class as a signal:

private SemaphoreSlim signal = new SemaphoreSlim(0, 1);

// set signal in event
signal.Release();

// wait for signal somewhere else
await signal.WaitAsync();

Alternatively, you can use an instance of the TaskCompletionSource<T> Class to create a Task<T> that represents the result of the button click:

private TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();

// complete task in event
tcs.SetResult(true);

// wait for task somewhere else
await tcs.Task;

How do I await events in C#?

Personally, I think that having async event handlers may not be the best design choice, not the least of which reason being the very problem you're having. With synchronous handlers, it's trivial to know when they complete.

That said, if for some reason you must or at least are strongly compelled to stick with this design, you can do it in an await-friendly way.

Your idea to register handlers and await them is a good one. However, I would suggest sticking with the existing event paradigm, as that will keep the expressiveness of events in your code. The main thing is that you have to deviate from the standard EventHandler-based delegate type, and use a delegate type that returns a Task so that you can await the handlers.

Here's a simple example illustrating what I mean:

class A
{
public event Func<object, EventArgs, Task> Shutdown;

public async Task OnShutdown()
{
Func<object, EventArgs, Task> handler = Shutdown;

if (handler == null)
{
return;
}

Delegate[] invocationList = handler.GetInvocationList();
Task[] handlerTasks = new Task[invocationList.Length];

for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, EventArgs, Task>)invocationList[i])(this, EventArgs.Empty);
}

await Task.WhenAll(handlerTasks);
}
}

The OnShutdown() method, after doing the standard "get local copy of the event delegate instance", first invokes all of the handlers, and then awaits all of the returned Tasks (having saved them to a local array as the handlers are invoked).

Here's a short console program illustrating the use:

class Program
{
static void Main(string[] args)
{
A a = new A();

a.Shutdown += Handler1;
a.Shutdown += Handler2;
a.Shutdown += Handler3;

a.OnShutdown().Wait();
}

static async Task Handler1(object sender, EventArgs e)
{
Console.WriteLine("Starting shutdown handler #1");
await Task.Delay(1000);
Console.WriteLine("Done with shutdown handler #1");
}

static async Task Handler2(object sender, EventArgs e)
{
Console.WriteLine("Starting shutdown handler #2");
await Task.Delay(5000);
Console.WriteLine("Done with shutdown handler #2");
}

static async Task Handler3(object sender, EventArgs e)
{
Console.WriteLine("Starting shutdown handler #3");
await Task.Delay(2000);
Console.WriteLine("Done with shutdown handler #3");
}
}

Having gone through this example, I now find myself wondering if there couldn't have been a way for C# to abstract this a bit. Maybe it would have been too complicated a change, but the current mix of the old-style void-returning event handlers and the new async/await feature does seem a bit awkward. The above works (and works well, IMHO), but it would have been nice to have better CLR and/or language support for the scenario (i.e. be able to await a multicast delegate and have the C# compiler turn that into a call to WhenAll()).

await and event handler

The syntax for asynchronous event handlers is :

Something.PropertyChanged += IsButtonVisible_PropertyChanged;  
...

private async void IsButtonVisible_PropertyChanged(object sender,
PropertyChangedEventArgs e)
{
if (IsSomethingEnabled)
{
await SomeService.ExecuteAsync(...);
}
}

This allows awaiting asynchronous operations inside the event handler without blocking the UI thread. This can't be used to await for an event in some other method though.

Awaiting a single event

If you want some other code to await for an event to complete you need a TaskCompletionSource. This is explained in Tasks and the Event-based Asynchronous Pattern (EAP).

public Task<string> OnPropChangeAsync(Something x)
{
var options=TaskCreationOptions.RunContinuationsAsynchronously;
var tcs = new TaskCompletionSource<string>(options);
x.OnPropertyChanged += onChanged;
return tcs.Task;

void onChanged(object sender,PropertyChangedEventArgs e)
{
tcs.TrySetResult(e.PropertyName);
x.OnPropertyChanged -= onChanged;
}

}

....

async Task MyAsyncMethod()
{
var sth=new Something();
....
var propName=await OnPropertyChangeAsync(sth);

if (propName=="Enabled" && IsSomethingEnabled)
{
await SomeService.ExecuteAsync(...);
}

}

This differs from the example in two places:

  1. The event handler delegate gets unregistered after the event fires. Otherwise the delegate would remain in memory as long as Something did.
  2. TaskCreationOptions.RunContinuationsAsynchronously ensures that any continuations will run on a separate thread. The default is to run them on the same thread that sets the result

This method will await only a single event. Calling it in a loop will create a new TCS each time, which is wasteful.

Awaiting a stream of events

It wasn't possible to easily await multiple events until IAsyncEnumerable was introduced in C# 8. With IAsyncEnumerable<T> and Channel, it's possible to create a method that will send a stream of notifications :

public IAsyncEnumerable<string> OnPropChangeAsync(Something x,CancellationToken token)
{
var channel=Channel.CreateUnbounded<string>();
//Finish on cancellation
token.Register(()=>channel.Writer.TryComplete());
x.OnPropertyChanged += onChanged;

return channel.Reader.ReadAllAsync();

async void onChanged(object sender,PropertyChangedEventArgs e)
{
channel.Writer.SendAsync(e.PropertyName);
}

}

....

async Task MyAsyncMethod(CancellationToken token)
{
var sth=new Something();
....
await foreach(var prop in OnPropertyChangeAsync(sth),token)
{

if (propName=="Enabled" && IsSomethingEnabled)
{
await SomeService.ExecuteAsync(...);
}
}

}

In this case, only one event handler is needed. Every time an event occurs the property named is pushed to the Channel. Channel.Reader.ReadAllAsync() is used to return an IAsyncEnumerable<string> that can be used to loop asynchronously. The loop will keep running until the CancellationToken is signaled, in which case the writer will go into the Completed state and the IAsyncEnumerable<T> will terminate.

How to 'await' raising an EventHandler event

Events don't mesh perfectly with async and await, as you've discovered.

The way UIs handle async events is different than what you're trying to do. The UI provides a SynchronizationContext to its async events, enabling them to resume on the UI thread. It does not ever "await" them.

Best Solution (IMO)

I think the best option is to build your own async-friendly pub/sub system, using AsyncCountdownEvent to know when all handlers have completed.

Lesser Solution #1

async void methods do notify their SynchronizationContext when they start and finish (by incrementing/decrementing the count of asynchronous operations). All UI SynchronizationContexts ignore these notifications, but you could build a wrapper that tracks it and returns when the count is zero.

Here's an example, using AsyncContext from my AsyncEx library:

SearchCommand = new RelayCommand(() => {
IsSearching = true;
if (SearchRequest != null)
{
AsyncContext.Run(() => SearchRequest(this, EventArgs.Empty));
}
IsSearching = false;
});

However, in this example the UI thread is not pumping messages while it's in Run.

Lesser Solution #2

You could also make your own SynchronizationContext based on a nested Dispatcher frame that pops itself when the count of asynchronous operations reaches zero. However, you then introduce re-entrancy problems; DoEvents was left out of WPF on purpose.

C# await an event

This would be a good use for TaskCompletionSource, which allows you to set up an awaitable object and set it in the callback, signaling the awaiter.

TaskCompletionSource<ErrorCode> source = null;

private void Sender_Recieve(object sender, ErrorCode e)
{
source.SetResult(e);
}

public async Task<ErrorCode> SendPacketAsync(Packet packet)
{
source = new TaskCompletionSource<ErrorCode>();
//send packet
return await source.Task;
}

Await on event handler

The problem here is that multiple instances of SomeEventHandler are running hence there are multiple Task values being created. The await call is only running on one of them hence it's somewhat up to chance as to whether or not it's theDoSomething method that ends up being awaited.

To fix this you will need to await on every Task value that is created

if (this.OnSomething != null) {
foreach (var d in this.OnSomething.GetInvocationList().Cast<SomeEventHandler>()) {
await d(args);
}
]

C# async/await for external event handler to fire

You would create a TaskCompletionSource for each waiting call, and then store this TaskCompletionSource in a lookup.

private readonly ConcurrentDictionary<long, TaskCompletionSource<AperatureValue>> _taskLookup = new ConcurrentDictionary<long, TaskCompletionSource<AperatureValue>>();

public Task<AperatureValue> GetCurrentAperatureValue()
{
long id = GenerateMessageId();

var taskCompletionSource = new TaskCompletionSource<AperatureValue>();
_taskLookup.TryAdd(id, taskCompletionSource);

//build the buffer here and send payload;
server.send("192.168.1.28", id, buf);

return taskCompletionSource.Task;
}

private void Server_MessageReceived(object sender, UDPMessageEventArgs e)
{
var newSequenceNum = BitConverter.ToInt32(e.sequenceNum);
if (this._taskLookup.TryRemove(
newSequenceNum,
out TaskCompletionSource<AperatureValue> taskCompletionSource
)
)
{
taskCompletionSource.SetResult(e.Value);
}
}

This gives you the basic approach. You would then need to deal with other factors like what happens if a corresponding server message doesn't come back in a reasonable time, and how do you log an error if the received server message does not have a corresponding call.

C# Await Multiple Events in Producer/Consumer

In this case, I would only use cancellation tokens for cancellation. A repeated timeout like a keep-alive timer is better represented as a timer.

So, I would model this as three cancelable tasks. First, the cancellation token:

All communication was cancelled by the network layer

CancellationToken token = ...;

Then, three concurrent operations:

A byte is received

var readByteTask = stream.ReadAsync(buf, 0, 1, token);

The keep-alive timer has expired

var keepAliveTimerTask = Task.Delay(TimeSpan.FromSeconds(10), token);

A message is available from the network thread

This one is a bit trickier. Your current code uses BlockingCollection<T>, which is not async-compatible. I recommend switching to TPL Dataflow's BufferBlock<T> or my own AsyncProducerConsumerQueue<T>, either of which can be used as async-compatible producer/consumer queues (meaning that the producer can be sync or async, and the consumer can be sync or async).

BufferBlock<byte[]> SendQueue = new ...;
...
var messageTask = SendQueue.ReceiveAsync(token);

Then you can use Task.WhenAny to determine which of these tasks completed:

var completedTask = await Task.WhenAny(readByteTask, keepAliveTimerTask, messageTask);

Now, you can retrieve results by comparing completedTask to the others and awaiting them:

if (completedTask == readByteTask)
{
// Throw an exception if there was a read error or cancellation.
await readByteTask;
var byte = buf[0];
...
// Continue reading
readByteTask = stream.ReadAsync(buf, 0, 1, token);
}
else if (completedTask == keepAliveTimerTask)
{
// Throw an exception if there was a cancellation.
await keepAliveTimerTask;
...
// Restart keepalive timer.
keepAliveTimerTask = Task.Delay(TimeSpan.FromSeconds(10), token);
}
else if (completedTask == messageTask)
{
// Throw an exception if there was a cancellation (or the SendQueue was marked as completed)
byte[] message = await messageTask;
...
// Continue reading
messageTask = SendQueue.ReceiveAsync(token);
}


Related Topics



Leave a reply



Submit