是否可以将异步方法作为 c# 中事件处理程序的回调?
Posted
技术标签:
【中文标题】是否可以将异步方法作为 c# 中事件处理程序的回调?【英文标题】:Is it possible to have async methods as callbacks to eventhandlers in c#? 【发布时间】:2013-10-21 22:49:32 【问题描述】:下面的例子说明了我的设计。有一个 while true 循环做某事并通过一个事件通知它已经对所有订阅者做了某事。我的应用程序在通知所有订阅者之前不应该继续执行,只要有人没有在回调中放置异步 void,它就可以工作。
如果有人在回调上放置了 async void 以等待某些任务,那么我的循环可以在回调完成之前继续。我可以做哪些其他设计来避免这种情况。
它的第 3 方插件注册主题并订阅事件,所以我无法控制他们是否放置了异步 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");
【问题讨论】:
下面两个答案都很好。我将标记投票最多的答案!我会标记他们两个,因为他们都对我帮助很大。 【参考方案1】:如果有人在回调上放置了 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);
【讨论】:
我想知道自定义add
和remove
是否比使用GetInvocationList()
更有意义,尤其是因为这可能是完全类型安全的。
@svick 类似上面的东西?
是的,我就是这个意思。
很有趣,感谢您的想法 :) 这正是我所追求的。是否有意义,以及如何扩展这种订阅者可以添加 void 方法和任务方法。可以使用 BeginInvoke 和 GetInvocationList。我想我会尝试自己玩一点。再次感谢。
@s093294 不作为事件 - 事件需要具有代表的固定签名。您可以使用 2 个事件(每个事件一个)并让人们订阅其中任何一个,但是一旦您允许 void 返回方法,您就会遇到最初的问题。【参考方案2】:
在通知所有订阅者之前,我的应用程序不应继续执行,只要有人没有在回调中放置 async void 即可。
我在designing for async
event handlers 上有一个博客条目。可以使用 Task
-returning 委托或将现有的 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 中得到了广泛认可,因此很可能被最终用户识别。
【讨论】:
这个实现不是意味着异步订阅者同时执行(至少在没有同步上下文的情况下)?我不确定这总是需要的。 如果没有同步上下文/任务调度程序,那么是的,处理程序可以并发执行。我不认为这是一个问题。如果应该有一个上下文,它将由框架提供。其他解决方案(例如,Task.WhenAll
)都具有相同的并发语义。以上是关于是否可以将异步方法作为 c# 中事件处理程序的回调?的主要内容,如果未能解决你的问题,请参考以下文章