5

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

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

它的第 3 方插件注册了主题并订阅了事件,所以我无法控制他们是否放置了 async void。可以理解,我无法为 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");

        }
    }
}
4

2 回答 2

4

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

您还可以按照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);
    }
}
于 2013-10-21T22:55:23.337 回答
3

我的应用程序在通知所有订阅者之前不应继续执行,只要有人不在回调上放置异步无效,它就可以工作。

我有一篇关于设计async事件处理程序的博客文章。可以使用Task-returning 委托或将现有委托包装SynchronizationContext在您自己的委托中(这将允许您检测并等待async void处理程序)。

但是,我建议您使用“延迟”,这是专门为解决 Windows 应用商店应用程序的这个问题而设计的对象。我的AsyncEx 库中有一个简单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 中已经很成熟,因此很可能被最终用户识别。

于 2013-10-22T01:35:32.847 回答