Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I await events in C#?

I am creating a class that has a series of events, one of them being GameShuttingDown. When this event is fired, I need to invoke the event handler. The point of this event is to notify users the game is shutting down and they need to save their data. The saves are awaitable, and events are not. So when the handler gets called, the game shuts down before the awaited handlers can complete.

public event EventHandler<EventArgs> GameShuttingDown;  public virtual async Task ShutdownGame() {     await this.NotifyGameShuttingDown();      await this.SaveWorlds();      this.NotifyGameShutDown(); }  private async Task SaveWorlds() {     foreach (DefaultWorld world in this.Worlds)     {         await this.worldService.SaveWorld(world);     } }  protected virtual void NotifyGameShuttingDown() {     var handler = this.GameShuttingDown;     if (handler == null)     {         return;     }      handler(this, new EventArgs()); } 

Event registration

// The game gets shut down before this completes because of the nature of how events work DefaultGame.GameShuttingDown += async (sender, args) => await this.repo.Save(blah); 

I understand that the signature for events are void EventName and so making it async is basically fire and forget. My engine makes heavy use of eventing to notify 3rd party developers (and multiple internal components) that events are taking place within the engine and letting them react to them.

Is there a good route to go down to replace eventing with something asynchronous based that I can use? I'm not sure if I should be using BeginShutdownGame and EndShutdownGame with callbacks, but that's a pain because then only the calling source can pass a callback, and not any 3rd party stuff that plugs in to the engine, which is what I am getting with events. If the server calls game.ShutdownGame(), there's no way for engine plugins and or other components within the engine to pass along their callbacks, unless I wire up some kind of registration method, keeping a collection of callbacks.

Any advice on what the preferred/recommended route to go down with this would be greatly appreciated! I have looked around and for the most part what I've seen is using the Begin/End approach which I don't think will satisfy what I'm wanting to do.

Edit

Another option I'm considering is using a registration method, that takes an awaitable callback. I iterate over all of the callbacks, grab their Task and await with a WhenAll.

private List<Func<Task>> ShutdownCallbacks = new List<Func<Task>>();  public void RegisterShutdownCallback(Func<Task> callback) {     this.ShutdownCallbacks.Add(callback); }  public async Task Shutdown() {     var callbackTasks = new List<Task>();     foreach(var callback in this.ShutdownCallbacks)     {         callbackTasks.Add(callback());     }      await Task.WhenAll(callbackTasks); } 
like image 637
Johnathon Sullinger Avatar asked Jan 04 '15 02:01

Johnathon Sullinger


People also ask

Which async method can be used for event handlers?

Since delegates (and events are delegates) implement the Asynchronous Programming Model (APM), you could use the TaskFactory. FromAsync method.

What is await Return C?

Syntax in c language: If only one child process is terminated, then return a wait() returns process ID of the terminated child process. If more than one child processes are terminated than wait() reap any arbitrarily child and return a process ID of that child process.

Are event handlers async?

NET events do not support async Task as a result type! Instead, you have to cast event handlers as async void if you want to run async code inside of an event handler method and to comply with the classic . NET event delegate signature.

Are events async C#?

Yes, they are synchronous.


2 Answers

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()).

like image 78
Peter Duniho Avatar answered Sep 29 '22 05:09

Peter Duniho


Peter's example is great, I've just simplified it a little using LINQ and extensions:

public static class AsynchronousEventExtensions {     public static Task Raise<TSource, TEventArgs>(this Func<TSource, TEventArgs, Task> handlers, TSource source, TEventArgs args)         where TEventArgs : EventArgs     {         if (handlers != null)         {             return Task.WhenAll(handlers.GetInvocationList()                 .OfType<Func<TSource, TEventArgs, Task>>()                 .Select(h => h(source, args)));         }          return Task.CompletedTask;     } } 

It may be a good idea to add a timeout. To raise the event call Raise extension:

public event Func<A, EventArgs, Task> Shutdown;  private async Task SomeMethod() {     ...      await Shutdown.Raise(this, EventArgs.Empty);      ... } 

But you have to be aware that, unlike synchronous evens, this implementation calls handlers concurrently. It can be an issue if handlers have to be executed strictly consecutively what they are often do, e.g. a next handler depends on results of the previous one:

someInstance.Shutdown += OnShutdown1; someInstance.Shutdown += OnShutdown2;  ...  private async Task OnShutdown1(SomeClass source, MyEventArgs args) {     if (!args.IsProcessed)     {         // An operation         await Task.Delay(123);         args.IsProcessed = true;     } }  private async Task OnShutdown2(SomeClass source, MyEventArgs args) {     // OnShutdown2 will start execution the moment OnShutdown1 hits await     // and will proceed to the operation, which is not the desired behavior.     // Or it can be just a concurrent DB query using the same connection     // which can result in an exception thrown base on the provider     // and connection string options     if (!args.IsProcessed)     {         // An operation         await Task.Delay(123);         args.IsProcessed = true;     } } 

You'd better change the extension method to call handlers consecutively:

public static class AsynchronousEventExtensions {     public static async Task Raise<TSource, TEventArgs>(this Func<TSource, TEventArgs, Task> handlers, TSource source, TEventArgs args)         where TEventArgs : EventArgs     {         if (handlers != null)         {             foreach (Func<TSource, TEventArgs, Task> handler in handlers.GetInvocationList())             {                 await handler(source, args);             }         }     } } 
like image 24
Kosta_Arnorsky Avatar answered Sep 29 '22 04:09

Kosta_Arnorsky