我正在创建一个包含一系列事件的类,其中一个是GameShuttingDown。当触发此事件时,我需要调用事件处理程序。该事件的目的是通知用户游戏正在关闭,他们需要保存其数据。可以等待保存,但不能保存事件。因此,当处理程序被调用时,游戏将在等待的处理程序完成之前关闭。
GameShuttingDown
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()); }
// 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);
我知道事件的签名是正确的void EventName,因此使它异步基本上是一发不可收拾。我的引擎大量使用事件通知第三者开发人员(和多个内部组件)事件正在引擎内发生,并让事件对事件做出反应。
void EventName
有什么好的方法可以用我可以使用的基于异步的东西替换事件?我不确定是否应该使用回调BeginShutdownGame并EndShutdownGame与之配合使用,但这很痛苦,因为只有调用源才能传递回调,而没有插入引擎的任何第三者材料,这就是我在事件中获得的东西。如果服务器调用game.ShutdownGame(),则引擎插件和引擎中的其他组件无法传递其回调,除非我采用某种注册方法,并保留了一系列回调。
BeginShutdownGame
EndShutdownGame
game.ShutdownGame()
任何关于采用哪种首选/推荐路线的建议都将不胜感激!我环顾四周,大部分情况下看到的是使用Begin / End方法,但我认为这不会满足我想要做的事情。
编辑
我正在考虑的另一个选择是使用注册方法,该方法需要等待回调。我遍历所有回调,获取它们的Task并等待一个WhenAll。
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); }
就我个人而言,我认为拥有async事件处理程序可能不是最佳的设计选择,其中至少一个原因就是您所遇到的问题。使用同步处理程序,知道它们何时完成很简单。
async
就是说,如果由于某种原因您必须或至少被迫坚持这种设计,则可以以await友好的方式进行。
await
您注册处理程序和await他们的想法是一个好主意。但是,我建议您坚持使用现有的事件范例,因为这将使事件在代码中保持可表达性。最主要的是,您必须偏离EventHandler基于标准的委托类型,并使用返回a的委托类型,Task以便可以await使用处理程序。
EventHandler
Task
这是一个简单的例子,说明我的意思:
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); } }
OnShutdown()在执行标准的“获取事件委托实例的本地副本”之后,该方法首先调用所有处理程序,然后等待所有返回的内容Tasks(在调用处理程序时将它们保存到本地数组中)。
OnShutdown()
Tasks
这是一个简短的控制台程序,说明了用法:
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"); } }
看完这个例子,我现在想知道C#是否没有办法抽象这一点。更改可能太复杂了,但是旧样式的void-returning事件处理程序和新的async/ await功能的当前组合确实有点尴尬。上面的方法可以正常工作(恕我直言,效果很好),但是对场景有更好的CLR和/或语言支持(即能够等待多播委托并将C#编译器将其转换为WhenAll())会很好。。
void
WhenAll()