在c#中是否可以使用异步方法作为EventHandler的回调?
下面的例子说明了我的设计。有一个while true循环在做某事,并通过事件通知它已对所有订阅者做了某事。在通知所有订阅者之前,我的应用程序不应该继续执行,只要有人没有在回调上放置一个异步void,这就可以工作 如果有人在回调上放置一个异步void以等待某个任务,那么我的循环可以在回调完成之前继续。我可以做什么其他设计来避免这种情况 它的第三方插件可以注册自己并订阅事件,因此我无法控制它们是否将异步作废。可以理解,我不能为EventHandler执行任务回调,那么对于.NET4.5我有什么选择呢在c#中是否可以使用异步方法作为EventHandler的回调?,c#,async-await,C#,Async Await,下面的例子说明了我的设计。有一个while true循环在做某事,并通过事件通知它已对所有订阅者做了某事。在通知所有订阅者之前,我的应用程序不应该继续执行,只要有人没有在回调上放置一个异步void,这就可以工作 如果有人在回调上放置一个异步void以等待某个任务,那么我的循环可以在回调完成之前继续。我可以做什么其他设计来避免这种情况 它的第三方插件可以注册自己并订阅事件,因此我无法控制它们是否将异步作废。可以理解,我不能为EventHandler执行任务回调,那么对于.NET4.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");
}
}
}
如果有人在回调上放置一个异步void以等待某个任务,那么我的循环可以在回调完成之前继续。我可以做什么其他设计来避免这种情况
这是无法避免的。即使您以某种方式“知道”订户不是通过async
/await
实现的,您仍然无法保证调用方没有构建某种形式的异步“操作”
例如,一个完全正常的void方法可以将其所有工作放入一个Task.Run
调用中
我的应用程序在通知所有订户之前不应继续执行
您当前的版本不遵循此合同。您正在同步通知订阅者-如果订阅者响应该通知以异步方式执行某些操作,则这超出了您的控制范围
可以理解,我不能为EventHandler执行任务回调,那么对于.NET4.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");
}
}
}
请注意,这实际上是可能的。例如,您可以将上述内容改写为:
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);
}
}
}
公共类程序
{
公共静态void Main()
{
var测试=新测试();
test.Event+=测试事件;
test.Event+=test_Event2;
test.DoneSomethingAsync().Wait();
}
}
公共委托任务CustomEvent(对象发送者,EventArgs e);
私有静态任务测试_Event2(对象发送方,EventArgs e)
{
控制台写入线(“代表2”);
返回Task.FromResult(false);
}
静态异步任务测试\事件(对象发送方、事件参数)
{
控制台写入线(“Del1gate 1”);
等待任务。延迟(5000);
控制台写入线(“5000毫秒后”);
}
公开课考试
{
公众活动;
公共异步任务DoneSomethingAsync()
{
var handler=this.Event;
if(处理程序!=null)
{
var tasks=handler.GetInvocationList().Cast().Select(s=>s(this,EventArgs.Empty));
等待任务。何时(任务);
}
}
}
您还可以使用事件添加/删除来重写此事件,建议如下:
公共类测试
{
私有列表事件=新列表();
公共活动定制活动
{
添加{lock(events)events.add(value);}
删除{lock(events)events.remove(value);}
}
公共异步任务完成方法()
{
列表处理程序;
锁定(事件)
handlers=this.events.ToList();//缓存此
var tasks=handlers.Select(s=>s(this,EventArgs.Empty));
等待任务。何时(任务);
}
}
在通知所有订阅者之前,我的应用程序不应该继续执行,只要有人没有在回调上放置一个异步void,这就可以工作
我在上有一篇博客。可以使用任务
-返回委托,也可以将现有的同步上下文
包装在您自己的内部(这将允许您检测并等待异步无效
处理程序)
但是,我建议您使用“延迟”,这是专门为解决Windows应用商店应用程序的此问题而设计的对象。my中提供了一个简单的延迟管理器
您的事件参数可以定义如下的getdeleral
方法:
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();
}
私有任务RaiseMyEventAsync()
{
var handler=MyEvent;
if(handler==null)
返回Task.FromResult(null);//或TaskConstants.Completed
var args=新的MyEventArgs(…);
处理程序(args);
返回args.waitfordeferralasync();
}
“延迟”模式的好处是,它在Windows应用商店API中已经建立,因此最终用户可能会识别它。我想知道,与使用
GetInvocationList()
相比,使用自定义add
和remove
是否更有意义,特别是因为这可能是完全类型安全的。@svick类似上面的东西?是的,这就是我的意思。有趣,谢谢你的想法:)这正是我想要的。这样的订阅者可以同时添加void方法和task方法。可能使用BeginInvoke和GetInvocationList。我想我会试着自己玩一玩。再次感谢。@s093294不作为事件-事件需要有代理的固定签名。您可以使用2个事件(每个事件一个)并让人们订阅其中一个,但一旦您允许void返回方法,您就有了初始问题。此实现是否意味着异步订阅服务器并发执行(至少在没有同步上下文的情况下)?我不确定这是否总是需要的。如果没有同步上下文/任务调度程序,那么是的,处理程序可以并发执行。我不认为这是个问题;如果应该有一个背景,它将由框架提供。其他解决方案(例如,Task.whalll
)都具有相同的并发语义。下面两个答案都非常好。我将在选举中选出得票最多的一个