直接回答这个问题:我认为EventHandler
不允许实现足够的通信来使调用者等待。你可能可以通过自定义同步上下文来进行一些技巧,但是如果您关心等待处理程序,则最好将处理程序能够将其Task
返回给调用者。通过将此作为委托签名的一部分,更清楚地表明委托将被await
。
我建议使用Delgate.GetInvocationList()
方法,该方法在Ariel的回答中描述与tzachs的回答中的想法相结合。定义自己的AsyncEventHandler<TEventArgs>
委托,它返回一个Task
。然后使用扩展方法隐藏正确调用它的复杂性。如果您想要执行一堆异步事件处理程序并等待它们的结果,我认为这种模式很有意义。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
public delegate Task AsyncEventHandler<TEventArgs>(
object sender,
TEventArgs e)
where TEventArgs : EventArgs;
public static class AsyncEventHandlerExtensions
{
public static IEnumerable<AsyncEventHandler<TEventArgs>> GetHandlers<TEventArgs>(
this AsyncEventHandler<TEventArgs> handler)
where TEventArgs : EventArgs
=> handler.GetInvocationList().Cast<AsyncEventHandler<TEventArgs>>();
public static Task InvokeAllAsync<TEventArgs>(
this AsyncEventHandler<TEventArgs> handler,
object sender,
TEventArgs e)
where TEventArgs : EventArgs
=> Task.WhenAll(
handler.GetHandlers()
.Select(handleAsync => handleAsync(sender, e)));
}
这使您可以创建一个普通的 .net 风格的
event
。只需像平常一样订阅它即可。
public event AsyncEventHandler<EventArgs> SomethingHappened;
public void SubscribeToMyOwnEventsForNoReason()
{
SomethingHappened += async (sender, e) =>
{
SomethingSynchronous();
await SomethingAsynchronousAsync();
SomeContinuation();
};
}
然后只需记住使用扩展方法来调用事件,而不是直接调用它们。如果您想在调用中获得更多控制权,则可以使用
GetHandlers()
扩展。对于等待所有处理程序完成的常见情况,只需使用方便的包装器
InvokeAllAsync()
即可。在许多模式中,事件要么不会产生任何调用者感兴趣的内容,要么通过修改传递的
EventArgs
向调用者通信。(请注意,如果您可以假设具有调度程序样式序列化的同步上下文,则您的事件处理程序可以在其同步块内安全地改变
EventArgs
,因为继续将被编组到分派线程上。例如,在WinForms或WPF的UI线程中调用和
await
事件时,这将自动发生。否则,如果您的任何突变发生在线程池上运行的连续体中,则可能必须在突变
EventArgs
时使用锁定)。
public async Task Run(string[] args)
{
if (SomethingHappened != null)
await SomethingHappened.InvokeAllAsync(this, EventArgs.Empty);
}
这使得调用事件看起来更像正常的事件调用,但你必须使用
.InvokeAllAsync()
。当然,你仍然需要处理事件的常规问题,例如需要保护事件调用以避免出现没有订阅者的事件引发
NullArgumentException
。
请注意,我
不使用
await SomethingHappened?.InvokeAllAsync(this, EventArgs.Empty)
,因为
await
在
null
上会出错。如果你愿意,可以使用以下调用模式,但可以争论括号很丑陋,而
if
风格通常更好,出于各种原因:
await (SomethingHappened?.InvokeAllAsync(this, EventArgs.Empty) ?? Task.CompletedTask)
AsyncEventHandler
。 - Shahin Dohan