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

本文关键字:事件处理 程序 回调 异步方法 是否 | 更新日期: 2023-09-27 18:36:16

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

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

它的第 3 方插件注册 themeself 并订阅事件,所以我无法控制它们是否放置了异步空白。可以理解的是,我无法为事件处理程序执行任务回调,那么我可以使用 .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");
        }
    }
}

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

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

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

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

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

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


可以理解的是,我

无法为事件处理程序执行任务回调,那么我可以使用 .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);
    }
}

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

我有一个关于设计async事件处理程序的博客文章。可以使用Task -return 委托或将现有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 中已建立,因此最终用户可能会识别它。