是否可以将异步方法作为 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");
}
}
}
如果有人在回调上放置了一个异步无效来等待某个任务,那么我的循环可以在回调完成之前继续。我还可以采取哪些其他设计来避免这种情况。
真的没有办法避免这种情况。 即使您以某种方式"知道"订阅者不是通过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 中已建立,因此最终用户可能会识别它。