非重播热观察

时间:2012-06-08 06:38:16

标签: c# .net system.reactive subject

原始问题

我有一个场景,我有多个IObservable序列,我想与Merge结合,然后听。但是,如果其中一个产生错误,我不希望它崩溃其他流的所有内容,以及重新订阅序列(这是一个'永久'序列)。

我这样做是在合并之前向流添加Retry(),即:

IEnumerable<IObservable<int>> observables = GetObservables();

observables
    .Select(o => o.Retry())
    .Merge()
    .Subscribe(/* Do subscription stuff */);

然而,当我想测试这个问题时会出现问题。我想测试的是,如果IObservable中的observables之一产生OnError,其他人应该仍然可以发送它们的值,它们应该被处理< / p>

我以为我只会在Subject<int>中使用两个IObservable代表两个observables;一个发送OnError(new Exception()),另一个发送OnNext(1)。但是,似乎Subject<int>将重播新订阅的所有先前值(有效Retry()),将测试变为无限循环。

我试图通过创建一个手册IObservable来解决它,这个手册var i = 0; var nErrors = 2; var testErrorObservableWithOneErrorAndThenCompletion = Observable.Create<int>(o => { i++; if (i < nErrors) { return Observable.Throw<int>(new Exception()).Subscribe(o); } else { return Observable.Empty<int>().Subscribe(o); } }); 会在第一个订阅上产生错误,之后会产生一个空序列,但是感觉很糟糕:

Subject

我使用Retry()或以错误的方式考虑Retry()吗?还有其他想法吗?你会如何解决这种情况?

更新

好的,这是我想要的和认为 o = message, X = error. ------o---o---X \ Retry() -> \---o---o---X \ Retry() -> \... 所做的大理石图。

Subject

我的问题可能更多,因为我没有一个好的库存类来使用预测试,因为Subject想要重播我以前的所有错误。

更新2

这是一个测试用例,显示我对Subject重放其值的含义。如果我说它以方式执行此操作,我是否正确使用该术语?我知道var onNext = false; var subject = new Subject<int>(); subject.Retry().Subscribe(x => onNext = true); subject.OnError(new Exception()); subject.OnNext(1); Assert.That(onNext, Is.True); 是一种创造热点可观察性的方式,但这种行为对我来说仍然感觉“冷”。

{{1}}

1 个答案:

答案 0 :(得分:4)

根据您更新的要求(您想要重试失败的可观察对象,而不是仅仅想忽略它们),我们可以提出一个有效的解决方案。

首先,理解冷可观察(在每个订阅上重新创建)和热可观察(无论订阅如何都存在)之间的区别很重要。您不能Retry()热门观察者,因为它不知道如何重新创建基础事件。也就是说,如果出现热的可观察错误,它就会永远消失。

Subject创建了一个热门观察者,在某种意义上说,您可以在没有订阅者的情况下调用OnNext并且它将按预期执行。要将热的observable转换为冷可观察对象,可以使用Observable.Defer,它将包含该可观察对象的“订阅时创建”逻辑。

所有这一切,这是修改后的原始代码:

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"));

测试(与以前类似):

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

按预期输出:

1
2
-1
done

当然,您需要根据您可观察到的内容来显着修改此概念。使用主题进行测试与将它们用于实际不同。

我还要注意这个评论:

  

然而,似乎主题将重播a的所有先前值   新订阅(有效地重试()),将测试转化为   无限循环。

不是真的 - Subject不会这样做。您的代码还有一些其他方面导致无限循环基于Retry重新创建订阅的事实,订阅会在某个时刻产生错误。


原始答案(完成时)

问题是Retry()没有做你想做的事。从这里开始:

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

  

重复源可观察序列retryCount次或直到   它成功终止了。

这意味着Retry将继续尝试重新连接到底层的observable,直到它成功并且不会抛出错误。

我的理解是你实际上希望observable中的异常被忽略,而不是重试。这将做你想要的事情:

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

这使用Catch来捕获带有异常的observable,并在那时用空的observable替换它。

以下是使用主题的完整测试:

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

这就像预期的那样产生了:

1
2
done