【问题标题】:awaitable event handler delegate for ASP.NETASP.NET 的可等待事件处理程序委托
【发布时间】:2021-07-29 04:18:36
【问题描述】:

我有一个 ASP.NET MVC/WebAPI 应用程序,其中域逻辑在某些情况下依赖于事件来解耦关注点。避免在事件处理程序中使用异步方法变得越来越困难,但由于这是一个 Web 应用程序,我想避免使用 async void,因为这些不是我们正在处理的顶级事件。我见过一些似乎过于复杂的解决方案来处理这个问题——我想保持这个简单的解决方案。我的解决方案是放弃 EventHandler 代表并使用返回 TaskFunc 代替,例如:

public event EventHandler<MyEventArgs> SomethingHappened;

将被重构为:

public event Func<object, MyEventArgs, Task> SomethingHappened;

所以在我的代码中我可以这样做:

if (SomethingHappened != null)
{
    await SomethingHappened.Invoke(this, new MyEventArgs());
}

我们是唯一使用这些项目的人,因此使用EventHandler 的标准约定并不是绝对必要的。虽然使用这种模式意味着知道处理程序是异步的,但我不确定这一定是一件坏事,因为越来越多的库正在放弃它们的同步 API 方法,或者根本不包括它们。在某种程度上,我很惊讶这在 .NET 中作为一流的概念不被支持,因为 async/await 在 Web 应用程序常用的许多库中变得越来越普遍。

这似乎是一个优雅的解决方案。我已经在具有多个事件订阅者的真实应用程序中对此进行了测试,每个处理程序具有不同的延迟,Invoke() 等待它们。然而,这感觉就像一个陷阱。我错过了什么?

【问题讨论】:

标签: c# asp.net .net async-await event-handling


【解决方案1】:

然而,这感觉就像一个陷阱。我错过了什么?

这部分不正确:

Invoke() 等待它们。

来自the docs

调用列表中包含多个条目的委托实例的调用过程是按顺序同步调用调用列表中的每个方法...如果委托调用包含输出参数或返回值,则它们的最终值将出现从列表中最后一个委托的调用开始。

因此,Invoke 将调用所有处理程序,但仅从 last 处理程序返回 Task。其他返回的任务将被忽略。这是非常糟糕的(尝试从被忽略的任务中抛出异常)。

相反,您应该调用GetInvocationList 来获取处理程序列表,调用每个处理程序,然后使用Task.WhenAllawait 它们全部:

var args = new MyEventArgs();
var tasks = SomethingHappened.GetInvocationList()
    .Cast<Func<object, MyEventArgs, Task>>()
    .Select(handler => handler(this, args))
    .ToList();
await Task.WhenAll(tasks);

【讨论】:

  • 就是我希望他的耳朵被烫到的那个人!我知道这是主观的,但这是“最好的”或至少是相当可接受的方式吗?有没有更好的办法?我喜欢上面的代码,因为它相当简单,我应该能够将它封装成简单且可重用的东西,但是我应该吗?我们开始觉得我们已经迟到了一些语法糖来处理这个问题,因为摆脱异步方法变得越来越困难。
  • @joelmdev:这是一种可接受的模式。我通常更喜欢deferrals,但它们更复杂。任务返回委托方法更容易(一旦您使用GetInvocationList 而不仅仅是Invoke)。
  • 一如既往地感谢斯蒂芬。我已经根据您的代码创建了一个通用扩展方法,该方法可用here
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-24
  • 1970-01-01
相关资源
最近更新 更多