是否可以将异步方法作为回调到c#中的事件处理程序?

Pou*_*sen 5 c# async-await

我的设计由下面的例子说明.有一段时间真正的循环做某事并通过事件通知它已经对所有订阅者做了一些事情.我的应用程序在完成通知所有订阅者之前不应该继续执行它,只要有人不在回调上放置异步空格就可以.

如果有人在回调上放置异步void以等待某个任务,那么我的循环可以在回调完成之前继续.我可以采取哪些其他设计来避免这种情况.

它的第3方插件注册了自己并订阅了这个事件,所以我无法控制它们是否存在异步空白.可以理解我不能为EventHandler做任务回调,所以我有什么替代品.net 4.5.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication4
{
    public class Test
    {

        public event EventHandler Event;

        public void DoneSomething()
        {
            if (Event != null)
                Event(this,EventArgs.Empty);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var test = new Test();

            test.Event += test_Event;
            test.Event +=test_Event2;

            while(true)
            {
                test.DoneSomething();
                Thread.Sleep(1000);

            }
        }

        private static void test_Event2(object sender, EventArgs e)
        {
            Console.WriteLine("delegate 2");
        }

        static async void test_Event(object sender, EventArgs e)
        {
            Console.WriteLine("Del1gate 1");

            await Task.Delay(5000);

            Console.WriteLine("5000 ms later");

        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Ree*_*sey 5

如果有人在回调上放置 async void 来等待某些任务,那么我的循环可以在回调完成之前继续。我还可以做哪些其他设计来避免这种情况。

确实没有办法避免这种情况。即使您以某种方式“知道”订阅者不是通过async/实现的await,您仍然无法保证调用者没有就地构建某种形式的异步“操作”。

例如,一个完全正常的 void 方法可以将其所有工作放入调用中Task.Run

我的应用程序在完成通知所有订阅者之前不应继续执行

您当前的版本确实遵循此合同。您同步通知订阅者 - 如果订阅者异步执行某些操作来响应该通知,则这是您无法控制的。


可以理解的是,我无法为 EventHandler 执行任务回调,那么 .net 4.5 有哪些替代方案。

请注意,这实际上是可能的。例如,您可以将上面的内容重写为:

public class Program
{
    public static void Main()
    {       
        var test = new Test();

        test.Event += test_Event;
        test.Event +=test_Event2;

        test.DoneSomethingAsync().Wait();
    }
}

public delegate Task CustomEvent(object sender, EventArgs e);

private static Task test_Event2(object sender, EventArgs e)
{
    Console.WriteLine("delegate 2");
    return Task.FromResult(false);
}

static async Task test_Event(object sender, EventArgs e)
{
    Console.WriteLine("Del1gate 1");
    await Task.Delay(5000);
    Console.WriteLine("5000 ms later");
}

public class Test
{
    public event CustomEvent Event;
    public async Task DoneSomethingAsync()
    {
        var handler = this.Event;
        if (handler != null)
        {
              var tasks = handler.GetInvocationList().Cast<CustomEvent>().Select(s => s(this, EventArgs.Empty));
              await Task.WhenAll(tasks);                
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您还可以按照svick的建议使用事件添加/删除来重写它:

public class Test
{
    private List<CustomEvent> events = new List<CustomEvent>();
    public event CustomEvent Event
    {
        add { lock(events) events.Add(value); }
        remove { lock(events) events.Remove(value); }
    }

    public async Task DoneSomething()
    {
        List<CustomEvent> handlers;
        lock(events) 
            handlers = this.events.ToList(); // Cache this
        var tasks = handlers.Select(s => s(this, EventArgs.Empty));
        await Task.WhenAll(tasks);
    }
}
Run Code Online (Sandbox Code Playgroud)