Non replaying hot observable

回眸只為那壹抹淺笑 提交于 2019-12-03 16:34:47

Based on your updated requirements (you want to retry the observables that fail, rather than just wanting to ignore them), we can come up with a solution that works.

First, it's important to understand the difference between a cold observable (recreated on every subscription) and a hot observable (exists regardless of subscriptions). You can't Retry() a hot observable, as it won't know how to recreate the underlying events. That is, if a hot observable errors, it's gone forever.

Subject creates a hot observable, in the sense that you can call OnNext without having subscribers and it will act as expected. To convert a hot observable to a cold observable, you can use Observable.Defer, which will contain the 'creation on subscription' logic for that observable.

All that said, here's the original code modified to do this:

var success = new Subject<int>();
var error = new Subject<int>();

var observables = new List<IObservable<int>> { Observable.Defer(() => {success = new Subject<int>(); return success.AsObservable();}), 
                                               Observable.Defer(() => {error = new Subject<int>(); return error.AsObservable();}) };                                            

observables
.Select(o => o.Retry())
.Merge()
.Subscribe(Console.WriteLine, Console.WriteLine, () => Console.WriteLine("done"));

And the test (similar to before):

success.OnNext(1);
error.OnError(new Exception("test"));
success.OnNext(2);
error.OnNext(-1);
success.OnCompleted();
error.OnCompleted();

And the output as expected:

1
2
-1
done

Of course, you'll need to modify this concept significantly depending on what you're underlying observable is. Using subjects for testing is not the same as using them for real.

I also want to note that this comment:

However, it seems Subject will replay all previous values for a new subscription (which effectively Retry() is), turning the test into an infinite loop.

Is not true - Subject doesn't behave this way. There is some other aspect of your code that is causing the infinite loop based on the fact that Retry recreates the subscription, and the subscription creates an error at some point.


Original answer (for completion)

The issue is that Retry() doesn't do what you want it to do. From here:

http://msdn.microsoft.com/en-us/library/ff708141(v=vs.92).aspx

Repeats the source observable sequence for retryCount times or until it successfully terminates.

This means that Retry will continually try and reconnect to the underlying observable until it succeeds and doesn't throw an error.

My understanding is that you actually want exceptions in the observable to be ignored, not retried. This will do what you want instead:

observables
.Select(o => o.Catch((Func<Exception,IObservable<int>>)(e => Observable.Empty<int>())))
.Merge()
.Subscribe(/* subscription code */);

This uses Catch to trap the observable with an exception, and replace it with an empty observable at that point.

Here is a full test using subjects:

var success = new Subject<int>();
var error = new Subject<int>();

var observables = new List<IObservable<int>> { success.AsObservable(), error.AsObservable() };

observables
.Select(o => o.Catch((Func<Exception,IObservable<int>>)(e => Observable.Empty<int>())))
.Merge()
.Subscribe(Observer.Create<int>(Console.WriteLine, Console.WriteLine, () => Console.WriteLine("done")));

success.OnNext(1);
error.OnError(new Exception("test"));
success.OnNext(2);
success.OnCompleted();

And this produces, as expected:

1
2
done
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!