【问题标题】:Bubble exceptions from async command events' handlers来自异步命令事件的处理程序的气泡异常
【发布时间】:2017-04-12 10:02:11
【问题描述】:

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

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

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

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

有没有办法做到这一点?

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

【问题讨论】:

  • .NET 中的事件设计在这里并不能很好地工作 - 事件意味着可以由多个处理程序订阅。这些处理程序需要进行自己的错误处理,因为如果某些处理程序成功处理然后其中一个抛出,这意味着什么?
  • 我同意,但是对于“命令式”事件,必须考虑例外情况。由于有信号返回源,因此也必须有某种方法可以发送回有效负载(有效负载是一个异常,可以重新抛出)。
  • @Damien_The_Unbeliever 是的,你说得对,正是这一点让我重新思考了设计……如果你有多个返回值或异常,你会重新抛出/处理哪一个?有点随意,所以这是一个糟糕的设计。

标签: c# events asynchronous async-await


【解决方案1】:

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

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

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

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

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

【讨论】:

  • 另一种不优雅但有效的方法是在事件的 args(包含延迟管理器的那个)中返回有效负载,它可以是值或异常列表等。丑陋但在紧要关头会起作用。需要是一个返回值列表,以便多个订阅者不会破坏彼此的返回值。 (不过,异常列表很棘手,因为你会抛出哪一个?:))
  • 是的,我认为您的所有观点都是有效的。我将相应地重新设计。再次感谢斯蒂芬。
猜你喜欢
  • 2011-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-07
  • 2013-01-16
  • 2014-12-02
  • 2014-01-30
相关资源
最近更新 更多