Rx Net - 处理错误并重新订阅可观察的

时间:2017-11-19 13:30:10

标签: c# .net exception events system.reactive

我计划将.Net事件替换为可观察事件。我喜欢Linq的方式,每个订阅者都可以以自己的方式过滤和转换数据。

默认情况下,Rx处理订阅并在管道中的某处抛出未处理的异常时关闭流(例如,在select,where,subscribe中)。因此,对onNext()的任何下一次调用都不会以订阅结束,因为它是由前一个异常处理的,并且不会通知订阅的监听器!

对于经典的.Net事件,这不是问题。每个事件调用都独立于任何其他事件,这意味着即使调用抛出异常,下一次调用也会让它自己成功运行。

理想情况下,我想使用EventLooperScheduler,因此对onNext的任何调用都不会等待订阅者完成他们的工作。

到目前为止,我已经得到了以下代码:

class Program
{
    static void Main(string[] args)
    {
        WriteLine("Main Thread {{THREAD_ID}}");

        RxEvent<int> myEvent = new RxEvent<int>(new EventLoopScheduler());

        IDisposable subscriptionA = myEvent.Stream
            .Where(x =>
            {
                //exception for testing purposes, this could be a null_reference, or a file_not_found, or a webrequest exception
                if (x.EventArgs == 2)
                    throw new ArgumentException();

                return true;
            })
            .Subscribe(x =>
            {
                //exception for testing purposes, this could be a null_reference, or a file_not_found, or a webrequest exception
                //comment out Where() exception to try this one
                if (x.EventArgs == 3)
                    throw new ArgumentException();

                WriteLine($"Subscriber A {{THREAD_ID}} Value: {x.EventArgs}");

                Thread.Sleep(500);
            },
            onError =>
            {
                var ex = onError;
            });

        IDisposable subscriptionB = myEvent.Stream.Subscribe(x =>
        {
            WriteLine($"Subscriber B {{THREAD_ID}} Value: {x.EventArgs}");
            Thread.Sleep(500);
        });

        for (var i = 1; i <= 7; i++)
        {
            myEvent.Invoke(null, i);
        }

        System.Console.WriteLine("END");
        System.Console.ReadLine();
    }

    private static void WriteLine(string str)
    {
        str = str.Replace("{THREAD_ID}", $"[Thread {Thread.CurrentThread.ManagedThreadId}]");
        System.Console.WriteLine(str);
    }
}

public class RxEvent<T>
{
    EventHandler<T> _Event;
    IObservable<EventPattern<T>> _Observable;
    IScheduler _Scheduler;

    public RxEvent()
        : this(CurrentThreadScheduler.Instance)
    {
    }

    public RxEvent(IScheduler scheduler)
    {
        _Scheduler = scheduler;
    }

    public IObservable<EventPattern<T>> Stream
    {
        get
        {
            if (_Observable == null)
                _Observable = Observable.FromEventPattern<T>(h => _Event += h, h => _Event -= h).ObserveOn(_Scheduler);

            return _Observable;
        }
    }

    public void Invoke(object sender, T args)
    {
        _Event?.Invoke(sender, args);
    }
}

当抛出第一个异常时,Where的实现会捕获它并只是处理订阅。相反,当抛出第二个异常(内部订阅)时,没有人处理它,并使线程停止。

我的问题是,是否有任何办法处理这些情况并保持订阅活着以便处理任何即将进行的调用,即使其中一些调用未处理的异常?

在我的观点中,当第一个异常是throw时,我得到以下输出。 (当第二个异常是抛出线程崩溃时):

Main Thread {[Thread 1]}
END
Subscriber A [Thread 4] Value: 1
Subscriber B [Thread 4] Value: 1
Subscriber B [Thread 4] Value: 2
Subscriber B [Thread 4] Value: 3
Subscriber B [Thread 4] Value: 4
Subscriber B [Thread 4] Value: 5
Subscriber B [Thread 4] Value: 6
Subscriber B [Thread 4] Value: 7

相反,我想得到:

Main Thread {[Thread 1]}
END
Subscriber A [Thread 4] Value: 1
Subscriber B [Thread 4] Value: 1
Subscriber B [Thread 4] Value: 2
Subscriber B [Thread 4] Value: 3
Subscriber A [Thread 4] Value: 4
Subscriber B [Thread 4] Value: 4
Subscriber A [Thread 4] Value: 5
Subscriber B [Thread 4] Value: 5
Subscriber A [Thread 4] Value: 6
Subscriber B [Thread 4] Value: 6
Subscriber A [Thread 4] Value: 7
Subscriber B [Thread 4] Value: 7

(值2和3会引发subscriberA的异常,因此不应打印任何内容)

我找到的相对资源是Rx - can/should I replace .NET events with Observables?

1 个答案:

答案 0 :(得分:0)

一个简单的规则:显式订阅中没有例外。

改为使用Do,这样,您还可以在Retry之后使用它来重新启动订阅。

this答案所示,还有Do的重载需要OnError处理程序:

source
    .Do(_ => { 
              throw new SomeException();
        }, 
            exception => Console.WriteLine(exception.ToString()) // log exceptions from source stream,
            () => {})
    .Retry()
    .Subscribe(
        l => Console.WriteLine($"OnNext {l}"),
        //      exception => Console.WriteLine(exception.ToString()), // Would be logging this in production
        () => Console.WriteLine("OnCompleted")
    );

您可以在末尾抛出Do().Retry(),它将起作用,并且您不会错过任何通知。请记住要处置订阅,否则它将等待OnCompleted信号。