在c#中是否可以使用异步方法作为EventHandler的回调?

在c#中是否可以使用异步方法作为EventHandler的回调?,c#,async-await,C#,Async Await,下面的例子说明了我的设计。有一个while true循环在做某事,并通过事件通知它已对所有订阅者做了某事。在通知所有订阅者之前,我的应用程序不应该继续执行,只要有人没有在回调上放置一个异步void,这就可以工作 如果有人在回调上放置一个异步void以等待某个任务,那么我的循环可以在回调完成之前继续。我可以做什么其他设计来避免这种情况 它的第三方插件可以注册自己并订阅事件,因此我无法控制它们是否将异步作废。可以理解,我不能为EventHandler执行任务回调,那么对于.NET4.5我有什么选择呢

下面的例子说明了我的设计。有一个while true循环在做某事,并通过事件通知它已对所有订阅者做了某事。在通知所有订阅者之前,我的应用程序不应该继续执行,只要有人没有在回调上放置一个异步void,这就可以工作

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

它的第三方插件可以注册自己并订阅事件,因此我无法控制它们是否将异步作废。可以理解,我不能为EventHandler执行任务回调,那么对于.NET4.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");

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

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

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

我的应用程序在通知所有订户之前不应继续执行

您当前的版本不遵循此合同。您正在同步通知订阅者-如果订阅者响应该通知以异步方式执行某些操作,则这超出了您的控制范围


可以理解,我不能为EventHandler执行任务回调,那么对于.NET4.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");

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

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);                
        }
    }
}
公共类程序
{
公共静态void Main()
{       
var测试=新测试();
test.Event+=测试事件;
test.Event+=test_Event2;
test.DoneSomethingAsync().Wait();
}
}
公共委托任务CustomEvent(对象发送者,EventArgs e);
私有静态任务测试_Event2(对象发送方,EventArgs e)
{
控制台写入线(“代表2”);
返回Task.FromResult(false);
}
静态异步任务测试\事件(对象发送方、事件参数)
{
控制台写入线(“Del1gate 1”);
等待任务。延迟(5000);
控制台写入线(“5000毫秒后”);
}
公开课考试
{
公众活动;
公共异步任务DoneSomethingAsync()
{
var handler=this.Event;
if(处理程序!=null)
{
var tasks=handler.GetInvocationList().Cast().Select(s=>s(this,EventArgs.Empty));
等待任务。何时(任务);
}
}
}
您还可以使用事件添加/删除来重写此事件,建议如下:

公共类测试
{
私有列表事件=新列表();
公共活动定制活动
{
添加{lock(events)events.add(value);}
删除{lock(events)events.remove(value);}
}
公共异步任务完成方法()
{
列表处理程序;
锁定(事件)
handlers=this.events.ToList();//缓存此
var tasks=handlers.Select(s=>s(this,EventArgs.Empty));
等待任务。何时(任务);
}
}
在通知所有订阅者之前,我的应用程序不应该继续执行,只要有人没有在回调上放置一个异步void,这就可以工作

我在上有一篇博客。可以使用
任务
-返回委托,也可以将现有的
同步上下文
包装在您自己的内部(这将允许您检测并等待
异步无效
处理程序)

但是,我建议您使用“延迟”,这是专门为解决Windows应用商店应用程序的此问题而设计的对象。my中提供了一个简单的
延迟管理器

您的事件参数可以定义如下的
getdeleral
方法:

public class MyEventArgs : EventArgs
{
  private readonly DeferralManager deferrals = new DeferralManager();

  ... // Your own constructors and properties.

  public IDisposable GetDeferral()
  {
    return deferrals.GetDeferral();
  }

  internal Task WaitForDeferralsAsync()
  {
    return deferrals.SignalAndWaitAsync();
  }
}
您可以引发事件并(异步)等待所有异步处理程序完成,如下所示:

private Task RaiseMyEventAsync()
{
  var handler = MyEvent;
  if (handler == null)
    return Task.FromResult<object>(null); // or TaskConstants.Completed

  var args = new MyEventArgs(...);
  handler(args);
  return args.WaitForDeferralsAsync();
}
私有任务RaiseMyEventAsync()
{
var handler=MyEvent;
if(handler==null)
返回Task.FromResult(null);//或TaskConstants.Completed
var args=新的MyEventArgs(…);
处理程序(args);
返回args.waitfordeferralasync();
}

“延迟”模式的好处是,它在Windows应用商店API中已经建立,因此最终用户可能会识别它。

我想知道,与使用
GetInvocationList()
相比,使用自定义
add
remove
是否更有意义,特别是因为这可能是完全类型安全的。@svick类似上面的东西?是的,这就是我的意思。有趣,谢谢你的想法:)这正是我想要的。这样的订阅者可以同时添加void方法和task方法。可能使用BeginInvoke和GetInvocationList。我想我会试着自己玩一玩。再次感谢。@s093294不作为事件-事件需要有代理的固定签名。您可以使用2个事件(每个事件一个)并让人们订阅其中一个,但一旦您允许void返回方法,您就有了初始问题。此实现是否意味着异步订阅服务器并发执行(至少在没有同步上下文的情况下)?我不确定这是否总是需要的。如果没有同步上下文/任务调度程序,那么是的,处理程序可以并发执行。我不认为这是个问题;如果应该有一个背景,它将由框架提供。其他解决方案(例如,
Task.whalll
)都具有相同的并发语义。下面两个答案都非常好。我将在选举中选出得票最多的一个