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

C# 使用ObserveOn时如何处理OnNext中的异常?,c#,system.reactive,C#,System.reactive,当我使用ObserveOn(Scheduler.ThreadPool)时,如果观察者在OnNext中抛出错误,我的应用程序将终止。我发现处理这个问题的唯一方法是使用下面的自定义扩展方法(除了确保OnNext从不抛出异常之外)。然后确保每个ObserveOn后面都有一个exceptionError public static IObservable<T> ExceptionToError<T>(this IObservable<T> source) {

当我使用
ObserveOn(Scheduler.ThreadPool)
时,如果观察者在
OnNext
中抛出错误,我的应用程序将终止。我发现处理这个问题的唯一方法是使用下面的自定义扩展方法(除了确保OnNext从不抛出异常之外)。然后确保每个
ObserveOn
后面都有一个
exceptionError

    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;
    }
public static IObservable exceptionError(此IObservable源){
var sub=新主题();
source.Subscribe(i=>{
试一试{
sub.OnNext(i);
}捕获(异常错误){
分包商(err);
}
}
,e=>sub.OnError(e),()=>sub.OnCompleted());
返回子节点;
}
然而,这感觉是不对的。有没有更好的方法来处理这个问题

示例

此程序因未捕获异常而崩溃

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();
        }
    }
}
类程序{
静态void Main(字符串[]参数){
试一试{
var xs=新主题();
ObserveOn(Scheduler.ThreadPool).Subscribe(x=>{
控制台写入线(x);
如果(x%5==0){
抛出新系统。异常(“砰!”);
}

},ex=>Console.WriteLine(“catch:+ex.Message”);//你说得对——感觉应该很糟糕。像这样使用和返回主题不是一个好方法

至少您应该这样实现此方法:

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;
    });
}
此代码正确捕获订阅中的错误


另一种方法是使用
物化
扩展方法,但除非上述解决方案不起作用,否则这可能有点过分。

订阅错误和可观察错误之间存在差异。快速测试:

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));
使用此命令运行,您将在订阅中获得一个未处理的错误:

xs.OnNext(1);
xs.OnNext(2);
xs.OnNext(3);
您的解决方案所做的是在订阅中获取错误,并在源代码中生成错误。。您在原始流中执行了此操作,而不是在每个订阅的基础上执行此操作。您可能有意这样做,也可能无意这样做,但这几乎肯定是错误的

“正确”的方法是将所需的错误处理直接添加到订阅操作中,订阅操作就属于该操作。如果不想直接修改订阅功能,可以使用一些帮助程序:

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

在评论中,我们开始讨论订阅中的错误指向流本身的错误这一事实,并且您不希望该流中有其他订阅者。这是一个完全不同类型的问题。我倾向于编写一个可观察的
验证
扩展来处理此场景:

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()
        );
    });
}

如果您仍然希望在订阅中抑制异常,则应使用讨论过的其他方法之一。

您当前的解决方案并不理想。如某位Rx人员所述:

Rx运营商不会捕获在调用OnNext、OnError或OnCompleted时发生的异常。这是因为我们希望(1)观察者实现者最了解如何处理这些异常,我们无法对它们做任何合理的处理;(2)如果发生异常,我们希望它冒泡出来,而不是由Rx处理

您当前的解决方案可以观察到由I观察员抛出的错误,这是没有意义的,因为语义上的I观测对象不应该知道观察它的东西。请考虑下面的例子:

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();
var errorFreeSource=新主题();
var sourceWithExceptionToError=errorFreeSource.ExceptionToError();
var Observer thatthows=Observer.Create(x=>
{
如果(x%5==0)
抛出新异常();
},
ex=>Console.WriteLine(“有一个参数认为应该调用它”),
()=>Console.WriteLine(“未完成”);
var observerThatWorks=Observer.Create(
x=>Console.WriteLine(“一切正常”),
ex=>Console.WriteLine(“但绝对不是这个”),
()=>Console.WriteLine(“未完成”);
SourceWithExceptionError.Subscribe(observerThatThrows);
SourceWithExceptionError.Subscribe(observerThatWorks);
errorFreeSource.OnNext(1);
errorFreeSource.OnNext(2);
errorFreeSource.OnNext(3);
errorFreeSource.OnNext(4);
errorFreeSource.OnNext(5);
Console.ReadLine();

在这里,源或运行的Observer没有问题,但由于与另一个观察者发生不相关的错误,将调用其OnError。要阻止另一个线程中的异常结束进程,您必须在该线程中捕获它们,因此在观察者中放一个try/catch块。

我们在Rx v2.0,st与RC发行版合作。你可以在我们的博客上阅读所有关于它的内容。它基本上归结为管道本身中更严格的错误处理,结合SubscribeSafe扩展方法(将订阅期间的错误重定向到OneError通道)和IsScheduler上的Catch扩展方法(使用异常处理逻辑包装计划程序,使其围绕计划的操作)

关于这里提出的ExceptionToError方法,它有一个缺陷。当回调运行时,IDisposable subscription对象仍然可以为null;这是一个基本的竞争条件。要解决这个问题,您必须使用SingleAssignmentDisposable。

我查看了本机方法来解决这个问题,但我不能让它工作。此方法有一个重载,它接受一个
IObserver

注意,
onError
处理程序中未处理的异常仍将使进程崩溃


仅当在
线程池上异步调用处理程序时引发异常在OneError示例中不会调用OneError,除非添加了ExceptionOnError(尝试使用console应用程序和单元测试项目)。您测试过吗?非常感谢
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));
xs.Subscribe(ActionAndCatch(Handler, ExceptionHandler), SourceExceptionHandler);
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()
        );
    });
}
xs
.Validate(x => x != 3)
.Subscribe(x => Console.WriteLine(x),
             ex => Console.WriteLine("Error in source: " + ex.Message));
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();
// 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);
/// <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;
}