【发布时间】:2021-12-17 07:22:07
【问题描述】:
最近我意识到 Rx Finally 运算符的行为方式至少对我来说是出乎意料的。我的期望是finallyAction 抛出的任何错误都会传播到下游的操作员观察者。唉,这不是发生的事情。在现实中,操作符 first 将先行序列的完成(或失败)传播给它的观察者,然后 then 调用action,在某个时间点不可能传播该操作引发的潜在错误。所以它会在ThreadPool 上抛出错误,并使进程崩溃。这不仅出乎意料,而且问题很大。以下是此行为的最小演示:
Observable
.Timer(TimeSpan.FromMilliseconds(100))
.Finally(() => throw new ApplicationException("Oops!"))
.Subscribe(_ => { }, ex => Console.WriteLine(ex.Message),
() => Console.WriteLine("Completed"));
Thread.Sleep(1000);
结果:未处理的异常 (Fiddle)
Finally lambda 引发的异常不会由 Subscribe:onError 处理程序处理,因为它是可取的。
这个功能(我很想称之为缺陷)严重限制了Finally 运算符在我眼中的实用性。本质上,我只能在我想调用一个预计永远不会失败的操作时使用它,如果它失败,则表明应用程序状态的灾难性损坏,而此时无法恢复。例如,我可以将它用于Release 和SemaphoreSlim(例如我已经完成here),如果我的代码有错误,它只会失败。在这种情况下,我的应用程序崩溃了。但我也使用它recently 来调用调用者提供的未知操作,该操作可能会失败,并且在这种情况下使应用程序崩溃是不可接受的。相反,错误应该传播到下游。所以我在这里要问的是如何实现具有相同签名的Finally 变体(我们称之为FinallySafe),以及下面指定的行为:
public static IObservable<TSource> FinallySafe<TSource>(
this IObservable<TSource> source, Action finallyAction);
- 应在
source序列发出OnCompleted或OnError通知之后调用finallyAction,但之前此通知传播到观察者。 - 如果
finallyAction调用成功完成,原始的OnCompleted/OnError通知应该传播给观察者。 - 如果
finallyAction调用失败,则应将OnError通知传播给观察者,其中包含刚刚发生的错误。在这种情况下,应该忽略(不传播)之前的错误,即可能导致source完成失败的错误。 -
finallyAction在source完成之前取消订阅时,也应该调用finallyAction。当订阅者(观察者)处理订阅时,finallyAction应该被同步调用,并且任何错误都应该传播给Dispose方法的调用者。 - 如果
FinallySafe被多个观察者订阅,则finallyAction应该在每个订阅中调用一次,对于每个订阅者来说都是独立的,遵循上述规则。并发调用没问题。 -
finallyAction不应为每个订阅者调用一次以上。
验证:将上面代码 sn-p 中的Finally 替换为FinallySafe,应该会导致程序不会因未处理的异常而崩溃。
替代方案:我也愿意接受一个可以合理解释为什么内置 Finally 运算符的行为比自定义 FinallySafe 运算符的行为更好的答案,如上所述。
【问题讨论】:
-
我不确定,但是否应该在异常后不调用 finally 块?诸如“读取文件并最终关闭它”之类的东西。这是 finally-block 内部的一个异常,不是使用它的方式。
-
@akop 这是一个很好的观点。在 Rx 世界中,尽管链接
Catch和Finally运算符是任何顺序都是可能的。这不是语言语法强加的固定顺序,就像在 C# 中一样。 -
“我的期望是
finallyAction抛出的任何错误都会传播到下游的操作员观察者。” - 这不应该是期望,因为 Rx 的合同只能有一个OnError或OnCompleted- 所以在序列完成后它不能有另一个错误。 -
“调用一个预期永远不会失败的动作”——这不只是编写健壮代码的一个例子吗?
-
@Enigmativity 编写防故障方法当然不是编写健壮代码的必要条件。否则,一半的内置 .NET API、记录为抛出
ArgumentExceptions、InvalidOperationExceptions、OperationCanceledExceptions 等的 API 将被视为非稳健 API。期望每个方法都能处理其调用期间可能发生的所有错误是不现实的。
标签: c# error-handling system.reactive rx.net