在C#中的异步事件 [英] Asynchronous events in C#

查看:152
本文介绍了在C#中的异步事件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我创建了一系列事件,他们是 GameShuttingDown 的一个类。当这个事件被触发,我需要调用的事件处理程序。本次活动的重点是通知用户游戏关闭,他们需要保存他们的数据。该节省的awaitable和事件都没有。因此,当处理器被调用,游戏中等待处理程序完成之前关闭

 公共事件的EventHandler< EventArgs的> GameShuttingDown; 

公共虚拟异步任务ShutdownGame()
{
等待this.NotifyGameShuttingDown();

等待this.SaveWorlds();

this.NotifyGameShutDown();
}

私人异步任务SaveWorlds()
{
的foreach(DefaultWorld世界this.Worlds)
{
等待this.worldService .SaveWorld(世界);
}
}

受保护的虚拟无效NotifyGameShuttingDown()
{
VAR处理器= this.GameShuttingDown;
如果(处理程序== NULL)
{
的回报;
}

处理程序(在此,新的EventArgs());
}



事件注册



  //本场比赛得到此之前关闭完成,因为事件是如何工作的
DefaultGame.GameShuttingDown + =异步(发件人,参数)=>的性质;等待this.repo.Save(等等);



据我了解,事件的签名是无效事件名称等等使得它异步基本上是火和忘记。我的引擎使得大量使用事件触发的通知的事件正在服用的引擎内的地方,让他们对此作出反应第三方开发者(和多个内部组件)。



有一个好的路线走下来基于异步我可以用它来代替的东西三项赛?我不知道我是否应该使用 BeginShutdownGame EndShutdownGame 有回调,但是这是一个痛苦,因为那时只有调用源可以传递一个回调,并没有任何第三方的东西,插入到引擎,这是我与事件得到。如果服务器调用 game.ShutdownGame(),也没有办法为发动机内部​​引擎插件和或其他部件沿着它们的回调传递,除非我线了某种形式的登记方法,保持回调的集合。



这是什么样的首选/推荐的路线走下来与此任何意见​​将不胜感激!我环顾四周,并为我所用的开始/结束的做法,我不认为会满足我想要做什么看到的大部分。



修改



我正在考虑使用一个注册方法,它接受一个awaitable回调的另一个选择。我遍历所有的回调,抓住他们的任务,并用 WhenAll

 <$等待C $ C>私人列表<&Func键LT;任务>> ShutdownCallbacks =新的List<&Func键LT;任务>>(); 

公共无效RegisterShutdownCallback(Func键<任务>回调)
{
this.ShutdownCallbacks.Add(回调);
}

公共异步任务关机()
{
变种callbackTasks =新的List<任务>();
的foreach(在this.ShutdownCallbacks VAR回调)
{
callbackTasks.Add(回调());
}

等待Task.WhenAll(callbackTasks);
}


解决方案

我个人认为有异步事件处理程序可能不是最好的设计选择,而不是其原因是您遇到的问题非常最少的。使用同步处理,是微不足道的知道他们什么时候完成。



这是说,如果由于某种原因必须或至少是强烈被迫坚持使用这种设计,可以这样做在等待 - 友好的方式。



您的想法注册处理程序和等待他们来说是一个很好的一个。不过,我建议用现有的事件范式坚持,因为这将让事件的表现在你的代码。最主要的是,你必须从标准事件处理程序基于委托类型偏离,并使用返回任务委托类型这样就可以了等待处理程序



下面是说明我的意思是一个简单的例子:

  A级
{
公共事件Func键<对象,EventArgs的,任务>关闭;

公共异步任务OnShutdown()
{
Func键<对象,EventArgs的,任务>处理器=关机;

如果(处理程序== NULL)
{
的回报;
}
$ B代表[] = invocationList handler.GetInvocationList()$ B;
任务[] = handlerTasks新任务[invocationList.Length]

的for(int i = 0; I< invocationList.Length;我++)
{
handlerTasks [I] =((Func键<对象,EventArgs的,任务>)invocationList [I])(这一点,EventArgs.Empty);
}

等待Task.WhenAll(handlerTasks);
}
}



OnShutdown()的方法,这样做的标准后,获得事件委托实例的本地副本,首先调用所有的处理程序,然后等待所有返回的任务(让他们保存的处理程序调用的本地阵列)



下面的说明使用短控制台程序:

 类节目
{
静态无效的主要(字串[] args)
{
A A =新的A() ;

a.Shutdown + = Handler1;
a.Shutdown + = Handler2;
a.Shutdown + = Handler3;

a.OnShutdown()等待()。
}

静态异步任务Handler1(对象发件人,EventArgs五)
{
Console.WriteLine(启动停机处理程序#1);
等待Task.Delay(1000);
Console.WriteLine(与关机处理器#1完成);
}

静态异步任务Handler2(对象发件人,EventArgs五)
{
Console.WriteLine(启动停机处理程序#2);
等待Task.Delay(5000);
Console.WriteLine(与停机处理程序#2完成);
}

静态异步任务Handler3(对象发件人,EventArgs五)
{
Console.WriteLine(启动停机处理程序#3);
等待Task.Delay(2000);
Console.WriteLine(与停机处理程序#3完成);
}
}



通过这个例子已经走了,现在我发现自己想知道如果不能对C#是一个方法,这个抽象一点。也许它会被太复杂的变化,但目前旧式的搭配无效 -returning事件处理程序和新的异步 / 的await 功能似乎有点尴尬。以上作品(并且效果很好,恕我直言),但它本来不错的的情况下更好的CLR和/或语言支持(即能够等候一个多播委托,并有C#编译器把它转换成一个调用 WhenAll())。


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);
}

解决方案

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

这篇关于在C#中的异步事件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆