如何等待C#中的事件?
我正在创建一个包含一系列事件的类,其中一个是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 workDefaultGame.GameShuttingDown += async (sender, args) => await this.repo.Save(blah);
我知道事件的签名是正确的void
EventName,因此使它异步基本上是一发不可收拾。我的引擎大量使用事件通知第三者开发人员(和多个内部组件)事件正在引擎内发生,并让事件对事件做出反应。
有什么好的方法可以用我可以使用的基于异步的东西替换事件?我不确定是否应该使用回调BeginShutdownGame
并EndShutdownGame
与之配合使用,但这很痛苦,因为只有调用源才能传递回调,而没有插入引擎的任何第三者材料,这就是我在事件中获得的东西。如果服务器调用game.ShutdownGame()
,则引擎插件和引擎中的其他组件无法传递其回调,除非我采用某种注册方法,并保留了一系列回调。
任何关于采用哪种首选/推荐路线的建议都将不胜感激!我环顾四周,大部分情况下看到的是使用Begin / End方法,但我认为这不会满足我想要做的事情。
我正在考虑的另一个选择是使用注册方法,该方法需要等待回调。我遍历所有回调,获取它们的Task并等待一个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
事件处理程序可能不是最佳的设计选择,其中至少一个原因就是您所遇到的问题。使用同步处理程序,知道它们何时完成很简单。
就是说,如果由于某种原因您必须或至少被迫坚持这种设计,则可以以await
友好的方式进行。
您注册处理程序和await
他们的想法是一个好主意。但是,我建议您坚持使用现有的事件范例,因为这将使事件在代码中保持可表达性。最主要的是,您必须偏离EventHandler
基于标准的委托类型,并使用返回a的委托类型,Task
以便可以await
使用处理程序。
这是一个简单的例子,说明我的意思:
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
(在调用处理程序时将它们保存到本地数组中)。
这是一个简短的控制台程序,说明了用法:
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()
)会很好。。
以上是 如何等待C#中的事件? 的全部内容, 来源链接: utcz.com/qa/425517.html