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

C# 压缩或合并两个IObservable序列,其中一个序列可能失败,c#,system.reactive,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 如果允许这些序列中的一个失败(抛出异常),但又不停止执行订阅的观察器,您将如何执行此操作

我有一个要求,可以看到两个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、Finally、next等以及如何将其应用于我的用例有点迷惑。非常感谢您的帮助。

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

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

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

现在,可以用OneRorResumeNext之类的东西来包装一个行为不好的源流,但是你必须有一个新的流,让你的包装器继续使用。。。第一条小溪死了

通常,在这种情况下,您将有一些潜在的热点事件源,您希望能够恢复“实时”(即,您不希望从第一个事件开始)

示例设置 我将模拟这个场景。不幸的是,将会有相当多的设置-但实际上创建这些可恢复流通常需要一些工作

我将首先创建一个工厂函数,该函数订阅每秒发出一次的人工热字母源。这将被调用以“恢复”对基础数据的订阅

首先,我们可以通过压缩带有字母数组的计时器来创建一个无错误的字母流:

var letters = new [] { "a", "b", "c", "d", "e", "f" };
var letterStream = Observable.Interval(TimeSpan.FromSeconds(1))
                             .Zip(letters, (_, ls) => ls);
现在,我们将
发布
这篇文章,让它成为热门话题-订阅发布的流将从任何地方获取它:

var hotLetterStream = letterStream.Publish();
现在我们可以创建一个observate,它将在订阅时订阅直播流,如果看到字母“c”,它将失败。这有点棘手,但这里不要太担心-这不是示例的重点,我们只需要一个流,它为我们提供底层热数据,并在特定值上失败。它展示了可观测流的特性,即它们只能出错一次

var failStream = hotLetterStream.SelectMany(x =>  x == "c"
        ? Observable.Throw<string>(new Exception()) 
        : Observable.Return(x));  
现在,我们可以将流与
Zip
相结合-我们使用
Catch
将失败的字母替换为“无值”单值流,然后
重复
将全新订阅无缝连接到热点源:

var combinedStream = numberStream.Zip(
    failStream.Catch<string, Exception>(ex => Observable.Return("NO VALUE"))
              .Repeat(), (ns,ls) => ns + " & " + ls);
最后,我们必须使用
Connect
来“打开”已发布的流,以启动值流:

hotLetterStream.Connect();
如果您拉入nuget package rx main并生成以下输出,则此代码将按编写的方式运行:

0 & a
1 & b
2 & NO VALUE
3 & d
通信错误 现在在这个简单的例子中,我们通过用“NO VALUE”字符串替换字母来传达错误。这个例子很好,可能对你有用。然而,在现实中,处理这样一个失败的流可能会导致对代码进行杂乱无章的检查

幸运的是,有一个干净的解决方案。您希望使用错误单子的概念。这在Haskell等语言中得到了本机支持,但在Rx中可以很容易地采用这一思想。它的工作原理是提供一个特殊的容器,很像.NET中的
Nullable
工作原理,但它不保存值或null,而是保存值或异常

它可以被认为是更一般的
单子的特化,单子有左右两个边。这是由Dave Sexton在其精彩的。该链接直接指向对
的讨论。创建自己的版本也很容易

因此,您不必订阅
IObservable
,而是将T包装在
IObservable
中。如果值是好的,则使用
other.Right
-如果值是坏的,则使用
other.Left
转发异常(这是一般惯例-例如,good“Right”bad“Left”)。您甚至可以创建一个
Error
类型来包装
,并将
TLeft
限制为异常。关心值的运算符可以检查左属性和右属性,而不只是传递值而忽略它是否为错误的运算符-就像.NET函数可以使用
null
而不必关心值是否为null一样

通过这种方式,您可以以一种清晰易懂的方式将异常沿着长长的可观察链传播到最终订户

这里的另一个要点是,区分您可能想要通信的数据中的错误和导致系统崩溃的流管道中的错误通常很重要。不要跳转到使用
OnError
来传递错误的数据段,因为这会杀死流。取而代之的是,只需在流中发送一个错误值。
本示例的完整代码要点如下:

非常感谢,目前无法消化,但稍后会有时间查看。虽然略读起来我很自信,但它似乎解决了我的问题。非常感谢!我认为你的帖子最终帮助我掌握了RX中的错误处理。明亮的
hotLetterStream.Connect();
0 & a
1 & b
2 & NO VALUE
3 & d