[Proposal] Deferred (async) events #2522
Replies: 15 comments
-
Why does this require syntactic sugar? Couldn't a library method solve this? I imagine it would logically look something like this: DeferredEventHandler<TEventArgs> Defer<TEventArgs>(Func<object, TEventArgs, Task> handler) =>
async (s, e) =>
{
var deferral = e.GetDeferral();
await handler(s, e.InnerEventArgs);
deferral.Complete();
}; Usage: MyEvent += Defer<InternalEventArgs>(MyHandler); (Note that the implementation of |
Beta Was this translation helpful? Give feedback.
-
I think that true async events in and of themselves would be useful, allowing you to invoke an event and await it. |
Beta Was this translation helpful? Give feedback.
-
@svick it's not a matter of actually needing this, but rather of being able to do it like so! Hence why it's just "syntactic sugaring"! @YairHalberstadt I can really relate to that, but I'm not trying here to rewrite the whole .NET event pattern, but rather having something that sits in the middle and doesn't bring any breaking changes of sorts! |
Beta Was this translation helpful? Give feedback.
-
@pedrolamas I think that when it comes to syntactic sugar, it's important how much code it saves. If it saves two lines of code that you have to repeat very often, that might be worth it. If it saves less than one line, then it's probably not worth it. And if a library method can get you from the former to the latter, then it's better to just have that method, because changing the language is expensive and requires a really good justification and no decent alternatives. |
Beta Was this translation helpful? Give feedback.
-
IMO what's missing here is event invocation. How should it work? Usual |
Beta Was this translation helpful? Give feedback.
-
@ghord absolutely correct, seems I forgot about that. I've just added the missing bits to the original post! |
Beta Was this translation helpful? Give feedback.
-
With the deferral pattern the event declaration has to anticipate that its handlers may want to complete asynchronously, by having event args that offer the deferral, and by using the deferral to determine that all the async work has completed. In other words, here as everywhere else, the asynchrony is an explicit two-way contract between the caller and the callee. If that's the case anyway then it doesn't seem particularly attractive, from a language standpoint, to support the "hacky" approach of a deferral. As already pointed out, some helper methods could probably get you to good enough here. It is interesting, though, to consider whether we could make "real" async events possible in the language. The delegate types would actually return The example above would be roughly this: // Delegate declaration
public Task SuspendingEventHandlerAsync(object sender, EventArgs args);
// Async event declaration
public async event SuspendingEventHandlerAsync SuspendingAsync;
// Handler
async protected Task OnSuspendingAsync(object sender, EventArgs args)
{
// No deferral needed
await SuspensionManager.SaveAsync();
}
// Event invocation
await SuspendingAsync(sender, args); // awaits all the Tasks |
Beta Was this translation helpful? Give feedback.
-
Event delegates already have Could that be used as the foundations for "real" async events, or would we have to start over and use |
Beta Was this translation helpful? Give feedback.
-
Curious how the "async" delegate would work and how you'd gather all of the Tasks, short of a runtime change. I guess C# could unroll the invocation list of the delegate, or it could manage a separate invocation list instead of using a single delegate field. Those methods are handled by the runtime and they dispatch through |
Beta Was this translation helpful? Give feedback.
-
@HaloFour |
Beta Was this translation helpful? Give feedback.
-
Quite frankly I'm fully on board with your (considerably better) proposal! My implementation was basically following the solution used in UWP given the current language support, but I would most definitely prefer the way you recommend! The fact is that there is a need for such asynchronous event handling (UWP has proved it, but most likely there are other places where the same applied) so it would be great this is something that the C# language team can get some time in the calendar to discuss - and I would be very interested in collaborating! 😁 |
Beta Was this translation helpful? Give feedback.
-
While I'm not opposed to the concept of making public class AsyncEventArgs : EventArgs {
private readonly List<Task> _tasks;
public void DoAsync(Func<Task> func) => _tasks.Add(func());
public async Task WhenAll() => Task.WhenAll(_tasks);
} You could then declare the event as follows: public class C {
public event EventHandler<AsyncEventArgs> MyEvent;
protected async Task OnMyEvent() {
var args = new AsyncEventArgs();
MyEvent?.Invoke(this, args);
await args.WhenAll();
}
} Then consumers could subscribe as follows: static class Program {
static void Main() {
var c = new C();
c.MyEvent += c_MyEvent;
}
private static void c_MyEvent(object sender, AsyncEventArgs e) {
e.DoAsync(async () => {
// do async stuff here
await Task.Delay(100);
});
}
// or if you want to be cute about it
private static void c_MyEvent(object sender, AsyncEventArgs e) => e.DoAsync(async () => {
// do async stuff here
await Task.Delay(100);
});
} Sure, it's slightly more work, but I don't think it's terribly onerous. If you need to pass additional arguments to the subscribers you can extend |
Beta Was this translation helpful? Give feedback.
-
@HaloFour Another alternate approach would be to use a custom "event backing" type, e.g.: private List<Func<MyParam, Task>> _handlers;
public event Func<MyParam, Task> MyEvent
{
add { _handlers.Add(value); }
remove { _handlers.Remove(value); }
}
private async Task InvokeMyEvent(MyParam arg)
{
foreach (var handler in _handlers)
await handler(arg);
} However, this does lose a lot of the "nice"-ness of working with events. I'd imagine that if |
Beta Was this translation helpful? Give feedback.
-
@FiniteReality You could do the same with an automatically implemented event, just call |
Beta Was this translation helpful? Give feedback.
-
I ran into this this week. Since I managed to forget how delegates work it led to this SO question. Based on Stephen's answer I came up with this extension method which I'll repost here for convenience:
I think the overall problem here is that without async event handlers being a first class concept within the language there is no defacto standard way of handling this. As a result, we end up with a lack of consistency that feels akin to the old days of vanilla javascript when each developer had their preferred workaround for faking private methods and variables; as a result we had to learn them all. Since more and more libraries lack synchronous counterparts for some of their methods, now including disposal, this problem will only grow with time. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Introduction
The active implementation of .NET events doesn't support any asynchronous handling. Any event handler will forcefully be
void
, so if a developer needs await for an asynchronous call, the full event handler signature will beasync void
.This pattern does bring some serious issues, particularly visible for UWP apps where the storage API's are asynchronous.
It's quite normal to save state when an app is being terminated/suspended, and for that, you would have event being raised to signal that action. But how can you asynchronously store state if the event handlers can't de awaited?
In UWP that is fixed by using the "deferred pattern". Let's take as an example the Application.Suspending event:
This event will require a standard
void
returning handler, but the "trick" is on the SuspendingEventArgs it supplies!The full async event handler will look like this:
Notice the
GetDeferral()
method call, which will signal the event raiser to wait for aComplete()
call.I've used this same pattern in my DeferredEvents NuGet package and would really like to see if this is something we could bring to the platform.
Proposal
What I would like is to extend the current "deferred events" pattern, by having some syntactic sugar coating on top of that!
Above there's a generic implementation for a
DeferredEventHandler
andDeferredEventArgs
To raise such events, I'd propose having an
InvokeAsync
method on the delegate. Something along the lines of this:Using these on any event would be easy to implement the "deferred events" pattern from UWP, but it would be even better if the compiler somehow did some automatic unwrapping of this in an event handler!
So this:
Would just become this:
Beta Was this translation helpful? Give feedback.
All reactions