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;
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 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:
- The event handler delegate gets unregistered after the event fires. Otherwise the delegate would remain in memory as long as
Something
did. 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.
Wait for a callback to be called in an async method
Take a look at this:
public async Task<string> GetImportantString()
{
string importantResult = null;
using (var sph = new SemaphoreSlim(0, 1))
{
await SubscribeToEvent("some event", async (message) =>
{
importantResult = message; // When "some event" happens, callback is called and we can set importantResult
sph.Release();
});
var t = sph.WaitAsync();
if (await Task.WhenAny(t, Task.Delay(10000)) == t)
{
return importantResult;
}
}
throw new TimeoutException(); // whatever you want to do here
}
We use a SemaphoreSlim
to signal when the string is set.
At that point, we await on Task.WhenAny
which lets us know when either the Semaphore releases or a delay-task elapses. If the Semaphore releases, we can safely assume the string has been set and return it.
Can we have an async Action?
My problem is that I randomly have silent crashes, and I now realize that this might be because the actions I call may (frequently are) async methods, and too late I am learning that exceptions in non awaited async methods are not caught anywhere.
Sort of. Exceptions thrown from normal asynchronous methods (i.e., ones that return Task
or Task<T>
) are just ignored if the task is never observed. Exceptions thrown from async void
methods do crash the process; this is a deliberate design decision.
Is there a way to define an Action as async,
Sure; just map delegates to function signatures, make them asynchronous, and then map back. That will give you the asynchronous delegate types (as described on my blog).
In this case, Action
maps to a method like void Handle()
; the asynchronous version of which is Task HandleAsync()
, which maps to a delegate of type Func<Task>
.
so you can await it when later calling it,
Welllll, sort of. Sure, await handler.Invoke();
will compile, but events (and delegates) can hold any number of handlers, and Invoke
just returns the last result, so the await
awaits the Task
returned from the last handler. If there are multiple handlers, all other Task
s are discarded (and their exceptions silently swallowed).
or might there be a smarter solution to this issue?
There's a few solutions for asynchronous events (link to my blog).
First, I'd consider adjusting the design. Events are a good match for the Observer design pattern, but the need to await
handlers usually means events are being misused to implement a different design pattern, usually the Command or Strategy design patterns. Consider replacing the event completely with something different, like an interface.
If that isn't feasible, then I'd consider using a different event representation by investigating Reactive Observables. Observables are really what events should be. But observables can get pretty complex.
Another approach is to use deferrals. Essentially, you keep the async void
method but the handlers need to "enlist" in asynchronous handling by acquiring a deferral in a using
. The invoking code then waits for all deferrals to be released.
The final approach is to use Func<Task>
, get all the tasks via GetInvocationList
, and await
them one at a time or with Task.WhenAll
.
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()
).
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 SynchronizationContext
s 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.
WinForms events executing out of order with async await
All solutions require something to block Shown from continuing until Load is completed. TaskCompletionSource
seems to be the most elegant way of accomplishing that.
Task _isLoadedTask;
private async void SomeForm_Load(object sender, EventArgs e)
{
var tcs = new TaskCompletionSource();
_isLoadedTask = tcs.Task;
someLabel.Text = "Start Load";
await SomeMethod();
someLabel.Text = "Finish Load";
tcs.TrySetResult();
}
private async void SomeForm_Shown(object sender, EventArgs e)
{
await _isLoadedTask;
someLabel.Text = "Shown";
}
Related Topics
Using .Net, How to Find the Mime Type of a File Based on the File Signature Not the Extension
Is There a Reason For C#'S Reuse of the Variable in a Foreach
Output Log Using Ftpwebrequest
How to Match an Entire String With a Regex
How to Escape a Double Quote in a Verbatim String Literal
Why Would You Use Expression≪Func≪T≫≫ Rather Than Func≪T≫
How to Convert Json Object to Custom C# Object
Difference Between I++ and ++I
How to Detect the Encoding/Codepage of a Text File
Passing Objects by Reference or Value in C#
Set Object Property Using Reflection
Use of Finalize/Dispose Method in C#
Getting "Type or Namespace Name Could Not Be Found" But Everything Seems Ok