C# asynchronous delegate: gracefully wait for asynchronous events
In C#, events traditionally return void, which makes them incompatible with asynchronous operations. This problem arises when an event handler needs to perform an asynchronous task before notifying other components. The following scenario explores this problem and provides a solution using an asynchronous delegate.
Question:
The code below demonstrates an event named GameShuttingDown
that fires when the game is closed. Each event handler should save data asynchronously before the game is closed. However, the handler is called with a void method, causing the game to close before the save completes.
<code class="language-csharp">public event EventHandler<EventArgs> GameShuttingDown; public 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()); }</code>
Solution:
To solve this problem, we can use an asynchronous delegate that returns a task. This way we can call the handler asynchronously and wait for the result.
Replace the existing GameShuttingDown
event with an asynchronous delegate type:
<code class="language-csharp">public event Func<object, EventArgs, Task> GameShuttingDown;</code>
Modify the NotifyGameShuttingDown
method to call the handler and wait for it to complete:
<code class="language-csharp">protected virtual async Task NotifyGameShuttingDown() { Func<object, EventArgs, Task> handler = GameShuttingDown; 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); }</code>
Usage:
Subscribe to GameShuttingDown
events using the new asynchronous delegate type:
<code class="language-csharp">DefaultGame.GameShuttingDown += async (sender, args) => await this.repo.Save(blah);</code>
This approach ensures that the game will only close after all asynchronous save operations in the event handler have completed.
The above is the detailed content of How to Handle Asynchronous Events in C# Using Async Delegates?. For more information, please follow other related articles on the PHP Chinese website!