【问题标题】:Reactive Extensions swallows exceptions from OnNext() called on a thread pool thread?反应式扩展吞下线程池线程上调用的 OnNext() 异常?
【发布时间】:2014-09-13 17:51:00
【问题描述】:

我在 .Net 4.5 中使用 Rx 2。当以下代码运行时,它只是静默退出,而不执行 OnCompleted 委托或显示任何错误。如果我在ToObservable 中使用Scheduler.CurrentThread,它至少会抛出错误并终止程序,此时不执行 OnCompleted 是有意义的。但是当这是在主线程以外的线程中执行时,这种行为似乎是不合理和不可接受的。我错过了什么吗?

static void Main()
{
     Enumerable.Range(0, 1)
                           .ToObservable(Scheduler.Default)
                           .Subscribe(o => { throw new Exception("blah"); }, () => Console.WriteLine("completed"));

     Thread.Sleep(2000);
 }

已编辑: 是的,当作为控制台应用程序运行时,无论在哪个线程上执行观察,它都会抛出错误。

但是,当我如下在 NUnit 中运行此代码作为测试时,它会在 2 秒(线程睡眠时间)后静默退出,没有任何错误或消息(预期“完成”)。那么实际上是 NUnit 导致了这个问题吗?

[TestFixture]
class Program
{
    [Test]
    public void Test()
    {
        Enumerable.Range(0, 1)
                .ToObservable(Scheduler.Default)
                .Subscribe(
                    o => { throw new Exception("blah"); }, 
                    () => Console.WriteLine("completed"));
        Thread.Sleep(2000);
    }
}

【问题讨论】:

    标签: c# system.reactive reactive-programming


    【解决方案1】:

    Rx 不会捕获观察者抛出的异常。这是一个非常重要的设计原则,之前已经详细讨论过,尽管出于某种原因,它仅作为 §6.4 的脚注包含在 Rx Design Guidelines 中。

    注意: 不保护对 SubscribeDisposeOnNextOnError 的调用em> 和 OnCompleted 方法。这些调用处于 monad 的边缘。从这些地方调用 OnError 方法会导致意外的行为。

    本质上,本指南确保,从观察者的角度来看,OnError 只会被源自可观察对象本身的异常调用,包括对直接参与计算的用户代码的任何调用(而不是仅仅观察结果)。如果不是这种情况,那么观察者可能无法区分传递给 OnError 的异常是其 OnNext 处理程序中的错误还是 observable 中的错误.

    但更重要的是,它还确保 OnNext 处理程序抛出的任何异常都未处理。这样可以更轻松地调试程序并保护用户数据。

    话虽如此,当 OnNext 在池线程上执行时,您可能会观察到不同行为的原因仅仅是您的调试经验的结果。尝试启用first-chance exceptions

    此外,我还可以通过将 Thread.Sleep 更改为 Console.ReadKey() 来避免竞争条件。

    【讨论】:

    • 还可以尝试在不附加调试器的情况下运行,您会看到预期的行为。
    • 谢谢,@DaveSexton。是的,作为控制台应用程序运行很好;实际上不是在 NUnit 测试中。请查看我编辑的问题。
    • 这是一个竞争条件。我已经说过你应该避免使用 Thread.Sleep。
    • 明确地说,您现在问的是一个完全不同的问题:“如何在 NUnit 中运行并发测试?”
    • 此外,您不应期望调用 OnCompleted。正如我所说,Rx 不会捕获观察者抛出的异常。结果,observable 立即停止。没有更多通知。
    【解决方案2】:

    在订阅块中抛出的异常具有未定义的行为。如果您正在做一些可以抛出的事情,您需要将其包装在 SelectSelectMany 中(或者只是将代码包装在 try-catch 中)。

    【讨论】:

    • 异常不是由 Subscribe 引发的,而是由 OnNext 引发的。 Rx 设计指南非常清楚地说明了这种行为:不处理异常。无论如何,我不确定 Select 会有什么帮助。它只会将异常推送到 OnError 中,他没有为其提供处理程序,因此无论如何它都会再次被抛出。 (这也违反了指南的建议。)
    • @DaveSexton 你说得对,它仍然会崩溃,我给出了更多关于当你想运行可能抛出的代码时该怎么做的一般性建议。
    • 那当然没问题,但我也特别不同意你的建议。这纯粹是建设性的批评。我在回答中解释说,Rx 中的建议是不捕获观察者抛出的异常并将它们重定向到 OnError。如果 OP 的观察者可能抛出,那么他们不应该更改为 Select 运算符以允许 Rx 捕获异常,因为 1)它使异常不太有用 2)它呈现 OnError 用处不大,并且容易出错 3) 仅出于此目的使用投影运算符在语义上是不正确的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多