Sometimes the event pattern is used to raise events in MVVM applications by or a child viewmodel to send a message to its parent viewmodel in a loosely coupled way like this.
Parent ViewModel
searchWidgetViewModel.SearchRequest += (s,e) =>
{
SearchOrders(searchWidgitViewModel.SearchCriteria);
};
SearchWidget ViewModel
public event EventHandler SearchRequest;
SearchCommand = new RelayCommand(() => {
IsSearching = true;
if (SearchRequest != null)
{
SearchRequest(this, EventArgs.Empty);
}
IsSearching = false;
});
In refactoring my application for .NET4.5 I am making as much as code possible to use async
and await
. However the following doesn't work (well I really wasn't expecting it to)
await SearchRequest(this, EventArgs.Empty);
The framework definitely does this to call event handlers such as this, but I'm not sure how it does it?
private async void button1_Click(object sender, RoutedEventArgs e)
{
textBlock1.Text = "Click Started";
await DoWork();
textBlock2.Text = "Click Finished";
}
Anything I've found on the subject of raising events asynchrously is ancient but I can't find something in the framework to support this.
How can I await
the calling of an event but remain on the UI thread.
Edit: This doesn't work well for multiple subscribers, so unless you only have one I wouldn't recommend using this.
Feels slightly hacky - but I have never found anything better:
Declare a delegate. This is identical to
EventHandler
but returns a task instead of voidYou can then run the following and as long as the handler declared in the parent uses
async
andawait
properly then this will run asynchronously:Sample handler :
Note: I've never tested this with multiple subscribers and not sure how this will work - so if you need multiple subscribers then make sure to test it carefully.
To answer the direct question: I do not think
EventHandler
allows implementations to communicate sufficiently back to the invoker to allow proper awaiting. You might be able to perform tricks with a custom synchronization context, but if you care about waiting for the handlers, it is better that the handlers are able to return theirTask
s back to the invoker. By making this part of the delegate’s signature, it is clearer that the delegate will beawait
ed.I suggest using the
Delgate.GetInvocationList()
approach described in Ariel’s answer mixed with ideas from tzachs’s answer. Define your ownAsyncEventHandler<TEventArgs>
delegate which returns aTask
. Then use an extension method to hide the complexity of invoking it correctly. I think this pattern makes sense if you want to execute a bunch of asynchronous event handlers and wait for their results.This allows you to create a normal .net-style
event
. Just subscribe to it as you normally would.Then simply remember to use the extension methods to invoke the event rather than invoking them directly. If you want more control in your invocation, you may use the
GetHandlers()
extension. For the more common case of waiting for all the handlers to complete, just use the convenience wrapperInvokeAllAsync()
. In many patterns, events either don’t produce anything the caller is interested in or they communicate back to the caller by modifying the passed inEventArgs
. (Note, if you can assume a synchronization context with dispatcher-style serialization, your event handlers may mutate theEventArgs
safely within their synchronous blocks because the continuations will be marshaled onto the dispatcher thread. This will magically happen for you if, for example, you invoke andawait
the event from a UI thread in winforms or WPF. Otherwise, you may have to use locking when mutatingEventArgs
in case if any of your mutations happen in a continuation which gets run on the threadpool).This gets you closer to something that looks like a normal event invocation, except that you have to use
.InvokeAllAsync()
. And, of course, you still have the normal issues that come with events such as needing to guard invocations for events with no subscribers to avoid aNullArgumentException
.Note that I am not using
await SomethingHappened?.InvokeAllAsync(this, EventArgs.Empty)
becauseawait
explodes onnull
. You could use the following call pattern if you want, but it can be argued that the parens are ugly and theif
style is generally better for various reasons:Since delegates (and events are delegates) implement the Asynchronous Programming Model (APM), you could use the TaskFactory.FromAsync method. (See also Tasks and the Asynchronous Programming Model (APM).)
The above code, however, will invoke the event on a thread pool thread, i.e. it will not capture the current synchronization context. If this is a problem, you could modify it as follows:
Based on Simon_Weaver's answer, I created a helper class that can handle multiple subscribers, and has a similar syntax to c# events.
To use it, you declare it in your class, for example:
To subscribe an event handler, you'll use the familiar syntax (the same as in Simon_Weaver's answer):
To invoke the event, use the same pattern we use for c# events (only with InvokeAsync):
If using c# 6, one should be able to use the null conditional operator and write this instead:
I'm not clear on what you mean by "How can I
await
the calling of an event but remain on the UI thread". Do you want the event handler to be executed on the UI thread? If that's the case then you can do something like this:Which wraps the invocation of the handler in a
Task
object so that you can useawait
, since you can't useawait
with avoid
method--which is where your compile error stems from.But, I'm not sure what benefit you expect to get out of that.
I think there's a fundamental design issue there. It's fine to kick of some background work on a click event and you can implement something that supports
await
. But, what's the effect on how the UI can be used? e.g. if you have aClick
handler that kicks off an operation that takes 2 seconds, do you want the user to be able to click that button while the operation is pending? Cancellation and timeout are additional complexities. I think much more understanding of the usability aspects needs to be done here.