如何在 C# 中等待事件? [英] How do I await events in C#?

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

问题描述

我正在创建一个包含一系列事件的类,其中一个是 GameShuttingDown.当这个事件被触发时,我需要调用事件处理程序.此事件的目的是通知用户游戏正在关闭,他们需要保存数据.保存是可等待的,而事件则不是.因此,当处理程序被调用时,游戏会在等待的处理程序完成之前关闭.

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

活动注册

// 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,因此使其异步基本上是一劳永逸.我的引擎大量使用事件来通知 3rd 方开发人员(和多个内部组件)引擎内正在发生事件并让他们对其做出反应.

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.

有没有好的方法可以用我可以使用的基于异步的东西替换事件?我不确定我是否应该将 BeginShutdownGameEndShutdownGame 与回调一起使用,但这很痛苦,因为只有调用源才能传递回调,而不能传递任何第三方插入引擎的东西,这就是我在事件中得到的东西.如果服务器调用 game.ShutdownGame(),引擎插件和/或引擎中的其他组件无法传递它们的回调,除非我连接某种注册方法,保留一组回调.

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.

编辑

我正在考虑的另一个选择是使用注册方法,它需要一个可等待的回调.我遍历所有回调,获取它们的 Task 并使用 WhenAll 等待.

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

推荐答案

就我个人而言,我认为拥有 async 事件处理程序可能不是最好的设计选择,其中最重要的原因是你遇到的问题.使用同步处理程序,知道它们何时完成是微不足道的.

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.

也就是说,如果由于某种原因您必须或至少被迫坚持这种设计,您可以以await友好的方式进行.

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.

您注册处理程序并await它们的想法是个好主意.但是,我建议坚持使用现有的事件范式,因为这将保持代码中事件的表现力.最主要的是你必须偏离标准的基于EventHandler的委托类型,使用返回一个Task的委托类型,这样你就可以await 处理程序.

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

OnShutdown() 方法在执行标准的获取事件委托实例的本地副本"后,首先调用所有处理程序,然后等待所有返回的Tasks(在调用处理程序时将它们保存到本地数组).

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

看完这个例子后,我现在发现自己想知道是否有办法让 C# 稍微抽象一下.也许改变太复杂了,但是旧式 void-returning 事件处理程序和新的 async/await 的当前组合> 功能似乎有点尴尬.上述工作(并且工作良好,恕我直言),但如果为场景提供更好的 CLR 和/或语言支持(即能够等待多播委托并让 C# 编译器将其转换为对 WhenAll()).

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天全站免登陆