且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何等待C#中的事件?

更新时间:2021-10-01 09:43:01

就我个人而言,我认为拥有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返回事件处理程序和新的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()).