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

时间:2013-10-21 22:49:32

标签: 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");

        }
    }
}

2 个答案:

答案 0 :(得分:4)

  

如果有人在回调上放置了异步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);                
        }
    }
}

您也可以使用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);
    }
}

答案 1 :(得分:3)

  

我的应用程序在完成通知所有订阅者之前不应该继续执行它,只要有人不在回调上放置异步空格就行。

我在designing for async event handlers上有一篇博客文章。可以使用Task - 返回委托或将现有的SynchronizationContext包装在您自己的内部(这样可以检测并等待async void个处理程序)。

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

您的事件参数可以定义GetDeferral方法:

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

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