来自异步命令事件处理程序的气泡异常



我需要安全地处理异步事件,而没有"隐式并行"的风险,因此使用他的Nito.AsyncEx/Nito.AsyncEx.Oop库和关于异步事件的出色教程实现了@StephenCleary建议的"延迟"机制。

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

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

但是,假设Validate()引发异常。我需要它向活动的制作人"冒泡"。换句话说,由于这是一个"命令事件",它会在完成后更新事件生产者,我希望异常也到达那里。但当然不是。

有没有办法做到这一点?

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

核心问题是由于我所说的"命令事件"。这些不是自然契合的,因为它们是通过使用事件实现策略模式,而事件旨在实现观察者模式。正是这种设计级别的不匹配使得"命令事件"难以处理。

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

由于您的应用程序需要更多的策略模式,因此有几个选项。第一种是尝试将其强制到事件实现中(例如,"任务返回委托解决方案":使您的事件类型Func<Task>,并让您的提升代码使用Delegate.GetInvocationListTask.WhenAll或围绕awaitforeach)。此方法的缺点是它强制所有处理程序都具有异步签名;同步处理程序可以返回Task.CompletedTask,所以这不是世界末日,但它有点丑陋。

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

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

最新更新