来自异步命令事件的异常'处理器

时间:2017-04-12 10:02:12

标签: c# events asynchronous async-await

我需要安全地处理异步事件而不存在"隐式并行性"的风险,因此实现了"延迟"机制advised by @StephenCleary,在异步事件中使用他的Nito.AsyncEx / Nito.AsyncEx.Oop库和great tutorial

效果很好。所以我的活动订阅者看起来像这样:

dbContext.SavingChanges += async (sender, e) => {
  using (e.GetDeferral()) {
    Audit();
    await Validate();   // this could throw
  }
};

但是,假设Validate()抛出异常。我需要它来"冒泡"参加活动的制作人。换句话说,因为这是一个"命令事件",它在事件生成器完成时更新它,我也希望例外也到达那里。但它当然不是。

有办法吗?

(背景:在db上下文保存之前,它会为处理程序引发事件以执行额外的工作,例如审计,验证。如果验证失败,我希望上下文捕获异常,以便它可以中止保存。)

1 个答案:

答案 0 :(得分:2)

核心问题是由于我称之为“命令事件”。这些不是很自然,因为他们通过使用事件来实现Strategy pattern,而事件旨在实现Observer pattern。正是这种设计级别的不匹配使得“命令事件”难以使用。

延迟模式是我对“命令事件”的首选,因为它与UAP应用程序中使用的延迟模式非常相似。但是,它确实假设事件是真正的事件 - 也就是说,async void方法在应用程序的“入门级”逻辑上运行。延迟允许您检测它们何时完成;它传播异常。异常(以及任何其他类型的结果)与Observer模式不兼容(因此使用事件实现起来很不方便)。

由于您的应用程序需要更多策略模式,因此有几种选择。第一种是尝试强制它进入事件实现(例如,“任务返回委托解决方案”:使您的事件类型为Func<Task>,并使您的提升代码使用Delegate.GetInvocationList和{{1}或Task.WhenAll周围的foreach。这种方法的缺点是它会强制所有处理程序都具有异步签名;同步处理程序可以返回await,所以它不是世界末日,但它有点难看。

另一种方法是以更常见的方式实现策略模式:使用接口。在您的情况下,您需要一个接口列表。这种方法的缺点在于它感觉就像你重新实现委托和事件一样,因为你有一个 list 的实现,而接口/类方法几乎都是只需Task.CompletedTask / Invoke

您采取的方法取决于您。这两种方法都有其自身的缺点。