ASP.NET 的可等待事件处理程序委托
awaitable event handler delegate for ASP.NET
我有一个 ASP.NET MVC/WebAPI 应用程序,其中域逻辑依赖于某些情况下的事件来解耦问题。在事件处理程序中避免使用异步方法变得越来越困难,但由于这是一个 Web 应用程序,我想避免使用 async void
,因为这些不是我们正在处理的顶级事件。我已经看到一些解决方案对于处理这个问题来说似乎过于复杂——我想让解决方案保持简单。我的解决方案是放弃 EventHandler
代表并使用 Func
代替 returns 和 Task
,例如:
public event EventHandler<MyEventArgs> SomethingHappened;
将重构为:
public event Func<object, MyEventArgs, Task> SomethingHappened;
所以在我的代码中我可以这样做:
if (SomethingHappened != null)
{
await SomethingHappened.Invoke(this, new MyEventArgs());
}
我们是唯一使用这些项目的人,因此使用 EventHandler
的标准约定并不是绝对必要的。虽然使用这种模式意味着知道处理程序是异步的,但我不确定这一定是件坏事,因为越来越多的库正在放弃它们的同步 API 方法,或者根本不包括它们。在某种程度上,令我感到惊讶的是,这作为 .NET 中的第一个 class 概念不受支持,async/await 在 Web 应用程序常用的许多库中变得越来越普遍。
这似乎是一个优雅的解决方案。我已经在具有多个事件订阅者的真实应用程序中对此进行了测试,每个处理程序都有不同的延迟,并且 Invoke()
等待他们。然而,这感觉就像一个陷阱。我错过了什么?
Yet, it feels like a trap. What am I missing?
这部分不正确:
Invoke() awaits them all.
来自the docs:
Invocation of a delegate instance whose invocation list contains multiple entries proceeds by invoking each of the methods in the invocation list, synchronously, in order... If the delegate invocation includes output parameters or a return value, their final value will come from the invocation of the last delegate in the list.
因此,Invoke
将调用所有处理程序,但仅 return 来自 last 处理程序的 Task
。其他 returned 任务将被忽略。这是非常糟糕的(尝试从一个被忽略的任务中抛出异常)。
相反,您应该调用 GetInvocationList
来获取处理程序列表,调用每个处理程序,然后 await
它们全部使用 Task.WhenAll
:
var args = new MyEventArgs();
var tasks = SomethingHappened.GetInvocationList()
.Cast<Func<object, MyEventArgs, Task>>()
.Select(handler => handler(this, args))
.ToList();
await Task.WhenAll(tasks);
我有一个 ASP.NET MVC/WebAPI 应用程序,其中域逻辑依赖于某些情况下的事件来解耦问题。在事件处理程序中避免使用异步方法变得越来越困难,但由于这是一个 Web 应用程序,我想避免使用 async void
,因为这些不是我们正在处理的顶级事件。我已经看到一些解决方案对于处理这个问题来说似乎过于复杂——我想让解决方案保持简单。我的解决方案是放弃 EventHandler
代表并使用 Func
代替 returns 和 Task
,例如:
public event EventHandler<MyEventArgs> SomethingHappened;
将重构为:
public event Func<object, MyEventArgs, Task> SomethingHappened;
所以在我的代码中我可以这样做:
if (SomethingHappened != null)
{
await SomethingHappened.Invoke(this, new MyEventArgs());
}
我们是唯一使用这些项目的人,因此使用 EventHandler
的标准约定并不是绝对必要的。虽然使用这种模式意味着知道处理程序是异步的,但我不确定这一定是件坏事,因为越来越多的库正在放弃它们的同步 API 方法,或者根本不包括它们。在某种程度上,令我感到惊讶的是,这作为 .NET 中的第一个 class 概念不受支持,async/await 在 Web 应用程序常用的许多库中变得越来越普遍。
这似乎是一个优雅的解决方案。我已经在具有多个事件订阅者的真实应用程序中对此进行了测试,每个处理程序都有不同的延迟,并且 Invoke()
等待他们。然而,这感觉就像一个陷阱。我错过了什么?
Yet, it feels like a trap. What am I missing?
这部分不正确:
Invoke() awaits them all.
来自the docs:
Invocation of a delegate instance whose invocation list contains multiple entries proceeds by invoking each of the methods in the invocation list, synchronously, in order... If the delegate invocation includes output parameters or a return value, their final value will come from the invocation of the last delegate in the list.
因此,Invoke
将调用所有处理程序,但仅 return 来自 last 处理程序的 Task
。其他 returned 任务将被忽略。这是非常糟糕的(尝试从一个被忽略的任务中抛出异常)。
相反,您应该调用 GetInvocationList
来获取处理程序列表,调用每个处理程序,然后 await
它们全部使用 Task.WhenAll
:
var args = new MyEventArgs();
var tasks = SomethingHappened.GetInvocationList()
.Cast<Func<object, MyEventArgs, Task>>()
.Select(handler => handler(this, args))
.ToList();
await Task.WhenAll(tasks);