C#中的asynchronous事件
我正在创build一个有一系列事件的类,其中一个是GameShuttingDown
。 当这个事件被触发时,我需要调用事件处理程序。 这个事件的关键是通知用户游戏正在closures,他们需要保存他们的数据。 节省是等待的,事件不是。 所以当处理程序被调用时,游戏在被等待的处理程序完成之前closures。
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
,所以使asynchronous基本上是火灾和遗忘。 我的引擎大量使用事件来通知第三方开发人员(以及多个内部组件)事件正在引擎中发生,并让他们对此作出反应。
是否有一个很好的途径去replaceasynchronous基于我可以使用的事件? 我不知道是否应该用callbackBeginShutdownGame
来使用BeginShutdownGame
和EndShutdownGame
,但这是一个痛苦,因为那么只有调用源可以传递一个callback,而不是任何插入到引擎的第三方的东西,这就是我所得到的与事件。 如果服务器调用game.ShutdownGame()
,那么引擎中的引擎插件和其他组件就无法通过callback传递,除非我连接某种注册方法,并保留一组callback。
任何build议什么首选/推荐路线下来,将不胜感激! 我已经环顾四周,大部分我看到的是使用开始/结束的方法,我不认为会满足我想做的事情。
编辑
我正在考虑的另一个select是使用注册方法,这需要等待callback。 我遍历所有的callback,抓住他们的任务,并等待与一个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
事件处理程序可能不是最好的deviseselect,不是最不重要的原因是你正在遇到的问题。 使用同步处理程序,知道它们何时完成是微不足道的。
也就是说,如果出于某种原因,您必须或至less强烈地坚持这个devise,您可以以一种await
友好的方式来做到这一点。
你的想法注册处理程序,并await
他们是一个很好的想法。 但是,我会build议坚持现有的事件模式,因为这会保持代码中事件的performance力。 主要的是你必须偏离标准的基于EventHandler
的委托types,并使用返回一个Task
的委托types,以便您可以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
function的组合似乎有些尴尬。 上面的工作(运行良好,恕我直言),但它会很好,有更好的CLR和/或语言支持的情况下(即能够等待一个多播委托,并让C#编译器把它变成一个调用WhenAll()
)。
我知道,操作系统是专门为此使用asynchronous和任务,但这是一个替代scheme,意味着处理程序不需要返回值。 代码基于Peter Duniho的例子。 首先是等同的A类(压扁了一下,以适应): –
class A { public delegate void ShutdownEventHandler(EventArgs e); public event ShutdownEventHandler ShutdownEvent; public void OnShutdownEvent(EventArgs e) { ShutdownEventHandler handler = ShutdownEvent; if (handler == null) { return; } Delegate[] invocationList = handler.GetInvocationList(); Parallel.ForEach<Delegate>(invocationList, (hndler) => { ((ShutdownEventHandler)hndler)(e); }); } }
一个简单的控制台应用程序来显示其使用
using System; using System.Threading; using System.Threading.Tasks; ... class Program { static void Main(string[] args) { A a = new A(); a.ShutdownEvent += Handler1; a.ShutdownEvent += Handler2; a.ShutdownEvent += Handler3; a.OnShutdownEvent(new EventArgs()); Console.WriteLine("Handlers should all be done now."); Console.ReadKey(); } static void handlerCore( int id, int offset, int num ) { Console.WriteLine("Starting shutdown handler #{0}", id); int step = 200; Thread.Sleep(offset); for( int i = 0; i < num; i += step) { Thread.Sleep(step); Console.WriteLine("...Handler #{0} working - {1}/{2}", id, i, num); } Console.WriteLine("Done with shutdown handler #{0}", id); } static void Handler1(EventArgs e) { handlerCore(1, 7, 5000); } static void Handler2(EventArgs e) { handlerCore(2, 5, 3000); } static void Handler3(EventArgs e) { handlerCore(3, 3, 1000); } }
我希望这对某人有用。
确实,事件本身就是不可预知的,所以你必须要解决这个问题。
我过去使用的一个解决scheme是使用信号量等待其中的所有条目被释放。 在我的情况下,我只有一个订阅的事件,所以我可以将其硬编码为new SemaphoreSlim(0, 1)
但在你的情况下,你可能想重写你的事件的getter / setter,并保持有多less用户的计数器,所以你可以dynamic设置最大并发线程数。
之后,您将信号量条目传递给每个订户,让他们做他们的事情,直到SemaphoreSlim.CurrentCount == amountOfSubscribers
(又名:所有景点已被释放)。
这将基本上阻止你的程序,直到所有的事件订阅者完成。
您可能还想考虑为您的订阅者提供一个GameShutDownFinished
事件,当他们完成游戏结束任务时,他们必须致电。 结合SemaphoreSlim.Release(int)
重载,现在可以清除所有信号量条目,并简单地使用Semaphore.Wait()
来阻塞线程。 现在不必检查所有条目是否已经被清除,而是等待一个点被释放(但是应该只有一个时刻所有点都被释放)。
internal static class EventExtensions { public static void InvokeAsync<TEventArgs>(this EventHandler<TEventArgs> @event, object sender, TEventArgs args, AsyncCallback ar, object userObject = null) where TEventArgs : class { var listeners = @event.GetInvocationList(); foreach (var t in listeners) { var handler = (EventHandler<TEventArgs>) t; handler.BeginInvoke(sender, args, ar, userObject); } } }
注意:这是asynchronous的,所以事件处理程序可能会危及UI线程。 事件处理程序(订阅者)不应该做UI工作。 否则就没什么意义了。
-
在你的活动提供者中声明你的事件:
公共事件EventHandler DoSomething;
-
调用您的提供者的事件:
DoSomething.InvokeAsync(new MyEventArgs(),this,ar => {完成时调用的callback函数(当需要的时候同步UI!)},null);
-
按照您通常的方式,由客户订阅活动