简体   繁体   English

使用ObserveOn时如何处理OnNext中的异常?

[英]How to handle exceptions in OnNext when using ObserveOn?

My application terminates when an error is thrown in OnNext by an observer when I use ObserveOn(Scheduler.ThreadPool) .当我使用ObserveOn(Scheduler.ThreadPool)时观察者在OnNext中抛出错误时,我的应用程序终止。 The only way I have found to deal with this is by using a custom extension method below (apart from making sure OnNext never throws an exception).我发现处理这个问题的唯一方法是使用下面的自定义扩展方法(除了确保 OnNext 永远不会抛出异常)。 And then making sure that each ObserveOn is followed by an ExceptionToError .然后确保每个ObserveOn后跟一个ExceptionToError

    public static IObservable<T> ExceptionToError<T>(this IObservable<T> source) {
        var sub = new Subject<T>();
        source.Subscribe(i => {
            try {
                sub.OnNext(i);
            } catch (Exception err) {
                sub.OnError(err);
            }
        }
            , e => sub.OnError(e), () => sub.OnCompleted());
        return sub;
    }

However, this does not feel right.但是,这感觉不对。 Is there a better way to deal with this?有没有更好的方法来处理这个问题?

Example例子

This program crashes because of uncaught exception.该程序因未捕获的异常而崩溃。

class Program {
    static void Main(string[] args) {
        try {
            var xs = new Subject<int>();

            xs.ObserveOn(Scheduler.ThreadPool).Subscribe(x => {
                Console.WriteLine(x);
                if (x % 5 == 0) {
                    throw new System.Exception("Bang!");
                }
            }, ex => Console.WriteLine("Caught:" + ex.Message)); // <- not reached

            xs.OnNext(1);
            xs.OnNext(2);
            xs.OnNext(3);
            xs.OnNext(4);
            xs.OnNext(5);
        } catch (Exception e) {
            Console.WriteLine("Caught : " + e.Message); // <- also not reached
        } finally {

            Console.ReadKey();
        }
    }
}

We're addressing this issue in Rx v2.0, starting with the RC release.从 RC 版本开始,我们在 Rx v2.0 中解决了这个问题。 You can read all about it on our blog at http://blogs.msdn.com/rxteam .您可以在我们的博客http://blogs.msdn.com/rxteam上阅读所有相关内容。 It basically boils down to more disciplined error handling in the pipeline itself, combined with a SubscribeSafe extension method (to redirect errors during subscription into the OnError channel), and a Catch extension method on IScheduler (to wrap a scheduler with exception handling logic around scheduled actions).它基本上归结为管道本身中更严格的错误处理,结合 SubscribeSafe 扩展方法(将订阅期间的错误重定向到 OnError 通道)和 IScheduler 上的 Catch 扩展方法(用异常处理逻辑包装调度程序行动)。

Concerning the ExceptionToError method proposed here, it has one flaw.关于这里提出的 ExceptionToError 方法,它有一个缺陷。 The IDisposable subscription object can still be null when the callbacks run;回调运行时,IDisposable 订阅对象仍然可以为 null; there's a fundamental race condition.有一个基本的竞争条件。 To work around this, you'd have to use a SingleAssignmentDisposable.要解决此问题,您必须使用 SingleAssignmentDisposable。

There's a difference between errors in subscription and errors in the observable.订阅错误和可观察错误之间是有区别的。 A quick test:快速测试:

var xs = new Subject<int>();

xs.Subscribe(x => { Console.WriteLine(x); if (x % 3 == 0) throw new System.Exception("Error in subscription"); }, 
             ex => Console.WriteLine("Error in source: " + ex.Message));

Run with this and you'll get a nice handled error in the source:运行这个,你会在源代码中得到一个很好的处理错误:

xs.OnNext(1);
xs.OnNext(2);
xs.OnError(new Exception("from source"));

Run with this and you'll get an unhandled error in the subscription:运行这个,你会在订阅中得到一个未处理的错误:

xs.OnNext(1);
xs.OnNext(2);
xs.OnNext(3);

What your solution has done is take errors in the subscription and make them errors in the source .您的解决方案所做的是接受订阅中的错误并使它们成为源中的错误 And you've done this on the original stream, rather than on a per subscription basis.而且你是在原始流上完成的,而不是在每个订阅的基础上。 You may or may not have intended to do this, but it's almost certainly wrong.您可能有意或无意这样做,但这几乎可以肯定是错误的。

The 'right' way to do it is to add the error handling you need directly to the subscribing action, which is where it belongs.做到这一点的“正确”方法是将您需要的错误处理直接添加到它所属的订阅操作中。 If you don't want to modify your subscription functions directly, you can use a little helper:如果你不想直接修改你的订阅功能,你可以使用一个小帮手:

public static Action<T> ActionAndCatch<T>(Action<T> action, Action<Exception> catchAction)
{
    return item =>
    {
        try { action(item); }
        catch (System.Exception e) { catchAction(e); }
    };
}

And now to use it, again showing the difference between the different errors:现在使用它,再次显示不同错误之间的区别:

xs.Subscribe(ActionAndCatch<int>(x => { Console.WriteLine(x); if (x % 3 == 0) throw new System.Exception("Error in subscription"); },
                                 ex => Console.WriteLine("Caught error in subscription: " + ex.Message)),
             ex => Console.WriteLine("Error in source: " + ex.Message));

Now we can handle (separately) errors in the source and error in the subscription.现在我们可以(分别)处理源中的错误和订阅中的错误。 Of course, any of these actions can be defined in a method, making the above code as simple as (potentially):当然,这些动作中的任何一个都可以在一个方法中定义,使上面的代码像(可能)一样简单:

xs.Subscribe(ActionAndCatch(Handler, ExceptionHandler), SourceExceptionHandler);

Edit编辑

In the comments we then started discussing the fact that errors in the subscription are pointing to errors in the stream itself, and you wouldn't want other subscribers on that stream.在评论中,我们随后开始讨论订阅中的错误指向流本身中的错误这一事实,您不希望该流上有其他订阅者。 This is a completely different type of issue .这是一个完全不同类型的问题 I would be inclined to write an observable Validate extension to handle this scenario:我倾向于编写一个可观察的Validate扩展来处理这种情况:

public static IObservable<T> Validate<T>(this IObservable<T> source, Predicate<T> valid)
{
    return Observable.Create<T>(o => {
        return source.Subscribe(
            x => {
                if (valid(x)) o.OnNext(x);
                else       o.OnError(new Exception("Could not validate: " + x));
            }, e => o.OnError(e), () => o.OnCompleted()
        );
    });
}

Then simple to use, without mixing metaphors (errors only in source):然后简单易用,无需混合隐喻(错误仅在源代码中):

xs
.Validate(x => x != 3)
.Subscribe(x => Console.WriteLine(x),
             ex => Console.WriteLine("Error in source: " + ex.Message));

If you still want suppressed exceptions in Subscribe you should use one of the other discussed methods.如果您仍然希望在Subscribe中抑制异常,您应该使用其他讨论的方法之一。

Your current solution is not ideal.您当前的解决方案并不理想。 As stated by one of the Rx people here :正如此处的一位 Rx 人员所述:

Rx operators do not catch exceptions that occur in a call to OnNext, OnError, or OnCompleted. Rx 运算符不会捕获调用 OnNext、OnError 或 OnCompleted 时发生的异常。 This is because we expect that (1) the observer implementor knows best how to handle those exceptions and we can't do anything reasonable with them and (2) if an exception occurs then we want that to bubble out and not be handled by Rx.这是因为我们期望(1)观察者实现者最了解如何处理这些异常,我们不能对它们做任何合理的事情,以及(2)如果发生异常,那么我们希望它冒出而不是由 Rx 处理.

Your current solution gets the IObservable to handle errors thrown by the IObserver, which doesn't make sense as semantically the IObservable should have no knowledge of the things observing it.您当前的解决方案让 IObservable 处理 IObserver 抛出的错误,这在语义上没有意义,因为 IObservable 不应该知道观察它的事物。 Consider the following example:考虑以下示例:

var errorFreeSource = new Subject<int>();
var sourceWithExceptionToError = errorFreeSource.ExceptionToError();
var observerThatThrows = Observer.Create<int>(x =>
  {
      if (x % 5 == 0)
          throw new Exception();
  },
  ex => Console.WriteLine("There's an argument that this should be called"),
  () => Console.WriteLine("OnCompleted"));
var observerThatWorks = Observer.Create<int>(
    x => Console.WriteLine("All good"),
    ex => Console.WriteLine("But definitely not this"),
    () => Console.WriteLine("OnCompleted"));
sourceWithExceptionToError.Subscribe(observerThatThrows);
sourceWithExceptionToError.Subscribe(observerThatWorks);
errorFreeSource.OnNext(1);
errorFreeSource.OnNext(2);
errorFreeSource.OnNext(3);
errorFreeSource.OnNext(4);
errorFreeSource.OnNext(5);
Console.ReadLine();

Here there is no issue with the source, or the observerThatWorks, but its OnError will be called due to an unrelated error with another Observer.这里没有源或 observerThatWorks 的问题,但由于与另一个观察者无关的错误,将调用它的 OnError。 To stop exceptions in a different thread from ending the process, you'll have to catch them in that thread, so put a try/catch block in your observers.要阻止不同线程中的异常结束进程,您必须在该线程中捕获它们,因此在您的观察者中放置一个 try/catch 块。

I looked at the native SubscribeSafe method that is supposed to solve this problem, but I can't make it work.我查看了本应解决此问题的本机SubscribeSafe方法,但我无法使其工作。 This method has a single overload that accepts an IObserver<T> :此方法有一个接受IObserver<T>的重载:

// Subscribes to the specified source, re-routing synchronous exceptions during
// invocation of the IObservable<T>.Subscribe(IObserver<T>) method to the
// observer's IObserver<T>.OnError(Exception) channel. This method is typically
// used when writing query operators.
public static IDisposable SubscribeSafe<T>(this IObservable<T> source,
    IObserver<T> observer);

I tried passing an observer created by the Observer.Create factory method, but the exceptions in the onNext handler continue crashing the process¹, just like they do with the normal Subscribe .我尝试传递一个由Observer.Create工厂方法创建的观察者,但onNext处理程序中的异常继续使进程崩溃¹,就像它们对普通Subscribe所做的一样。 So I ended up writing my own version of SubscribeSafe .所以我最终编写了自己的SubscribeSafe版本。 This one accepts three handlers as arguments, and funnels any exceptions thrown by the onNext and onCompleted handlers to the onError handler.这个接受三个处理程序作为参数,并将onNextonCompleted处理程序抛出的任何异常传递给onError处理程序。

/// <summary>Subscribes an element handler, an error handler, and a completion
/// handler to an observable sequence. Any exceptions thrown by the element or
/// the completion handler are propagated through the error handler.</summary>
public static IDisposable SubscribeSafe<T>(this IObservable<T> source,
    Action<T> onNext, Action<Exception> onError, Action onCompleted)
{
    // Arguments validation omitted
    var disposable = new SingleAssignmentDisposable();
    disposable.Disposable = source.Subscribe(
        value =>
        {
            try { onNext(value); } catch (Exception ex) { onError(ex); disposable.Dispose(); }
        }, onError, () =>
        {
            try { onCompleted(); } catch (Exception ex) { onError(ex); }
        }
    );
    return disposable;
}

Beware, an unhandled exception in the onError handler will still crash the process!当心, onError处理程序中未处理的异常仍然会使进程崩溃!

¹ Only exceptions thrown when the handler is invoked asynchronously on the ThreadPool . ¹只有在ThreadPool上异步调用处理程序时才会抛出异常。

You're right - it should feel bad.你是对的 - 它应该感觉很糟糕。 Using and returning subjects like this is not a good way to go.像这样使用和返回主题不是一个好方法。

At the very least you should implement this method like so:至少你应该像这样实现这个方法:

public static IObservable<T> ExceptionToError<T>(this IObservable<T> source)
{
    return Observable.Create<T>(o =>
    {
        var subscription = (IDisposable)null;
        subscription = source.Subscribe(x =>
        {
            try
            {
                o.OnNext(x);
            }
            catch (Exception ex)
            {
                o.OnError(ex);
                subscription.Dispose();
            }
        }, e => o.OnError(e), () => o.OnCompleted());
        return subscription;
    });
}

Notice there are no subjects used and that if I catch an error that I then dispose of the subscription to prevent the sequence continuing past an error.请注意,没有使用任何主题,如果我发现一个错误,我将处理订阅以防止序列继续发生错误。

However, why not just add an OnError handler in your subscription.但是,为什么不在您的订阅中添加一个OnError处理程序。 A bit like this:有点像这样:

var xs = new Subject<int>();

xs.ObserveOn(Scheduler.ThreadPool).Subscribe(x =>
{
    Console.WriteLine(x);
    if (x % 5 == 0)
    {
        throw new System.Exception("Bang!");
    }
}, ex => Console.WriteLine(ex.Message));

xs.OnNext(1);
xs.OnNext(2);
xs.OnNext(3);
xs.OnNext(4);
xs.OnNext(5);

This code catches the error properly in the subscription.此代码在订阅中正确捕获错误。

The other method is to use the Materialize extension method, but that may be a little overkill unless the above solutions don't work.另一种方法是使用Materialize扩展方法,但这可能有点矫枉过正,除非上述解决方案不起作用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM