压缩或合并两个IObservable序列,其中一个序列可能失败

时间:2014-10-17 08:57:30

标签: c# system.reactive

我有一个要求,即看到两个IObservable序列的合并,但这些序列之一可能会失败,而不会影响观察者。

所以从简介到Rx一书的例子中,将一系列数字和字符压缩在一起:

nums  --0--1--2|

chars --a--b--c--d--e--f|

result -0--1--2|

        a  b  c|
Prints:
0 & a
1 & b
2 & c

你将如何做到这一点,但允许其中一个序列失败(抛出异常)但不停止订阅观察者的执行?

nums  --0--1--2--3|

chars --a--b--X--d--e--f|

result -0--1--2--3|

        a  b  X  d|
Prints:
0 & a
1 & b
2 & NO VALUE
3 & d

我有点使用Catch,最后,OnErrorResumeNext等,以及如何将其应用于我的用例。所有人都非常感谢。

1 个答案:

答案 0 :(得分:3)

牢记Rx语法。序列定义如下:

OnNext* (OnError | OnCompleted)

换句话说,序列具有零个或多个OnNext事件,后跟OnError或OnCompleted。 (这忽略了时间方面,因为它可能是一个无限长的流,所以从未完成的流仍然有效。)

关键是流最多只能有一个错误。一旦发送OnError,就不会再发送任何事件。

现在,可以使用类似OnErrorResumeNext的东西来包装性能不佳的源流 - 但是你必须有一个新的流来让你的包装器继续......第一个流已经死了。

通常情况下,在这种情况下,您将拥有某种潜在的热门事件来源,您希望能够恢复这些事件。[34]生活" (也就是说,你不想从第一次活动开始)。

示例设置

我将模拟这种情况。不幸的是,这将是一个相当数量的设置 - 但实际上创建这些可恢复的流通常需要一些工作!

我将首先创建一个工厂函数,该函数可以订阅每秒发出一次的人为热字源。这将被称为" recover"订阅基础数据。

首先,我们可以通过使用字母数组压缩计时器来创建无错字母流:

var letters = new [] { "a", "b", "c", "d", "e", "f" };
var letterStream = Observable.Interval(TimeSpan.FromSeconds(1))
                             .Zip(letters, (_, ls) => ls);

现在,我们将Publish这个让它变得热门 - 对已发布的流的订阅将从它到达的任何地方获取:

var hotLetterStream = letterStream.Publish();

现在我们可以创建一个observable,在订阅时订阅实时流,如果它看到字母" c"则会失败。这有点棘手,但在这里不要太担心 - 它不是示例的主要内容,我们只需要一个流,它为我们提供底层的热数据并在特定值上失败。它展示了可观察流的属性,因为它们只能出错一次。

var failStream = hotLetterStream.SelectMany(x =>  x == "c"
        ? Observable.Throw<string>(new Exception()) 
        : Observable.Return(x));  

现在我们可以设置数字流 - 它只返回一个从零开始的值,持续4秒:

var numberStream = Observable.Interval(TimeSpan.FromSeconds(1)).Take(4);

现在我们可以将流与Zip合并 - 我们使用Catch将失败的字母替换为&#34; NO VALUE&#34;单值流,然后Repeat将全新订阅无缝连接到热源:

var combinedStream = numberStream.Zip(
    failStream.Catch<string, Exception>(ex => Observable.Return("NO VALUE"))
              .Repeat(), (ns,ls) => ns + " & " + ls);

现在我们可以订阅:

combinedStream.Subscribe(Console.WriteLine);

最后我们必须&#34;开启&#34;已发布的流Connect以启动值流:

hotLetterStream.Connect();

如果您拉入nuget包rx-main并生成以下输出,则此代码按照编写的方式运行:

0 & a
1 & b
2 & NO VALUE
3 & d

传达错误

现在在这个简单的例子中,我们通过用&#34; NO VALUE&#34;替换字母来传达错误。串。这个例子很好,可能适合你。但实际上,处理这样的故障流可能会导致整个代码的检查混乱。

幸运的是,有一个干净的解决方案。您想使用Error monad的想法。这在Haskell这样的语言中是原生支持的,但这个想法可以在Rx中轻松采用。它的工作原理是提供一个特殊的容器 - 很像Nullable<T>在.NET中工作 - 但它不是保持值或null,而是保存值或异常。

它可以被认为是更通用的Either<TLeft, TRight> monad的特化 - 它具有左侧和右侧。这是由Dave Sexton在他精彩的Rxx extension to Rx中实现的。该链接直接讨论Either。创建自己的版本也很容易。

因此,不是订阅IObservable<T>而是将您的T包裹在IObservable<Either<TException, T>>中。如果值很好,请使用Either.Right发送 - 如果不正确使用Either.Left转发例外(这是一般惯例 - 例如好&#34;右&#34;坏&#34;离开&#34;)。您甚至可以创建一个Error类型来包装Either并将TLeft限制为异常。关心价值的运算符可以检查左右属性,运算符不会传递值,而不管它是否是错误 - 正如.NET函数可以使用{{1无需关心值是否为空。

通过这种方式,您可以以冗长且易于理解的方式将冗长可观察链中的异常传播给最终订阅者。

这里的另一个要点是,区分您可能想要通信的数据中的错误以及拆除系统的流管道中的错误通常很重要。不要跳转到使用Nullable<T>来传达错误的数据 - 因为这会杀死流。相反,请考虑只在流中发送错误值。

这个示例的完整代码的要点是:https://gist.github.com/james-world/904ca7383a8f1cd349b9