C# 如果我在Subscribe回调中为一个可观察对象抛出异常,会发生什么?

C# 如果我在Subscribe回调中为一个可观察对象抛出异常,会发生什么?,c#,.net,system.reactive,C#,.net,System.reactive,我正在使用最新版本,遇到了一个设计问题: 如果我从传递给Subscribe的委托抛出异常,会发生什么 通过源代码步进,我发现: 受试者将忽略例外情况 从Producer派生的运算符(例如其中)在异常通过时处理订阅 当然,我已经发现,无论我在哪里通过一个标准的RX操作符传递一个可观察到的,任何异常都会导致我的事件因为处理而停止。至少,除非我重新订阅 这让我怀疑我的设计。从我的代理中抛出异常是个坏主意吗?显然,RX团队也这么认为。(尽管我怀疑默默地处理“坏”订阅是否正确。) 但是,看看我的设计,

我正在使用最新版本,遇到了一个设计问题:

如果我从传递给Subscribe的委托抛出异常,会发生什么

通过源代码步进,我发现:

  • 受试者将忽略例外情况
  • 从Producer派生的运算符(例如
    其中
    )在异常通过时处理订阅
当然,我已经发现,无论我在哪里通过一个标准的RX操作符传递一个可观察到的,任何异常都会导致我的事件因为处理而停止。至少,除非我重新订阅

这让我怀疑我的设计。从我的代理中抛出异常是个坏主意吗?显然,RX团队也这么认为。(尽管我怀疑默默地处理“坏”订阅是否正确。)

但是,看看我的设计,我不明白为什么这是个问题。我有一些受保护的操作正在进行,我开始启动一些OnNext来通知侦听器(我们已经完全从旧的skool.NET事件切换到了Observables),如果其中出现任何错误,它将抛出堆栈,直到碰到处理程序为止。在我的例子中,处理程序回滚它正在处理的事务,这也会通知侦听器回滚。它是异常安全的,工作正常。至少,在Where运营商的生产基地没有进行处理的情况下,它可以正常工作

再往前走一点。。受试者和同伴不这样做是否不一致?对于我们自己的iSubject和我们在这里编写的可观察操作符,我们是否应该对异常行为执行相同的dispose操作


我期待任何见解

好的,我在来源中找到了我的答案。只需将其粘贴在此处,以供子孙后代使用

// Safeguarding of the pipeline against rogue observers is required for proper
// resource cleanup. Consider the following example:
//
//   var xs  = Observable.Interval(TimeSpan.FromSeconds(1));
//   var ys  = <some random sequence>;
//   var res = xs.CombineLatest(ys, (x, y) => x + y);
//
// The marble diagram of the query above looks as follows:
//
//   xs  -----0-----1-----2-----3-----4-----5-----6-----7-----8-----9---...
//                  |     |     |     |     |     |     |     |     |
//   ys  --------4--+--5--+-----+--2--+--1--+-----+-----+--0--+-----+---...
//               |  |  |  |     |  |  |  |  |     |     |  |  |     |
//               v  v  v  v     v  v  v  v  v     v     v  v  v     v
//   res --------4--5--6--7-----8--5--6--5--6-----7-----8--7--8-----9---...
//                                 |
//                                @#&
//
// Notice the free-threaded nature of Rx, where messages on the resulting sequence
// are produced by either of the two input sequences to CombineLatest.
//
// Now assume an exception happens in the OnNext callback for the observer of res,
// at the indicated point marked with @#& above. The callback runs in the context
// of ys, so the exception will take down the scheduler thread of ys. This by
// itself is a problem (that can be mitigated by a Catch operator on IScheduler),
// but notice how the timer that produces xs is kept alive.
//为确保正常运行,需要保护管道免受流氓观察员的攻击
//资源清理。考虑下面的例子:
//
//var xs=可观测的时间间隔(时间跨度从秒(1));
//变量ys=;
//var res=xs.combinelatetest(ys,(x,y)=>x+y);
//
//上面查询的大理石图如下所示:
//
//xs------0------1------2------3------4------5------6------7------8------9------。。。
//                  |     |     |     |     |     |     |     |     |
//ys-------4--+--5--+--2--+--1--+--+--+--0--+--+--+--。。。
//               |  |  |  |     |  |  |  |  |     |     |  |  |     |
//v v v v v v v v v v v v
//res------4--5--6--7--8--5--6--7--8--8--9--。。。
//                                 |
//                                @#&
//
//请注意Rx的自由线程特性,其中消息位于结果序列上
//由两个输入序列中的任意一个生成,以进行组合测试。
//
//现在假设res的观测者在OnNext回调中发生异常,
//在上面用@#标记的指示点。回调在上下文中运行
//的调度程序线程,因此异常将关闭ys的调度程序线程。这个
//本身就是一个问题(可以通过IsScheduler上的Catch操作员来缓解),
//但是请注意,生成xs的计时器是如何保持活动的。

所以答案是:是的,在OnNext中抛出异常是个坏主意。一般来说在我的特定情况下,我知道这没问题,所以我将找到另一种方法。

在出现问题时抛出异常。当你认为你能解决他们指出的问题时,抓住他们。对异常处理的语言支持假设您有一个函数调用堆栈,因此异常只是爬升堆栈寻找处理程序

但请记住,在使用Rx时,处理模型已被侧置。您不再拥有源代码(调用代码)位于顶部、观察者(调用代码)位于底部的深层函数调用堆栈。因此,您不能依靠语言在Rx流中做正确的事情

如果您的回调抛出异常,那么Rx倾向于捕获该异常,并通过可观察对象的
OnError
通道传递它。如果订阅时未提供
OnError
处理程序,则Rx会在后台线程上引发异常,从而在应用程序中生成未处理的异常

Rx不会通知数据源下游出现异常。这是因为数据源与数据使用者完全隔离。它可能不在同一进程上,甚至不在同一台机器上,也可能不是用同一种语言编写的。这就是
主题
什么都不做的原因。
主题
在本例中充当数据源,并不真正关心观察者做什么

正如您所注意到的,默认情况下,未捕获的异常将导致Rx取消您的观察者对可观察对象的订阅。这是快速故障原理,也是Rx能够做出的唯一安全假设。如果您的观察者提出了一个异常,那么一定是出了问题,默认情况下,我们不应该给它提供更多的数据。Rx提供的机制允许您以不一定取消订阅可观察对象的方式处理异常

一种方法是将异常转化为数据:

// instead of:
source.Where(foo => predicateThatMightThrowException(foo)).Subscribe(foo => ..., error => ...)

// do:
source.Select(foo =>
{
    try { return new { foo: foo, filter: predicateThatMightThrowException(foo), error: (Exception)null }; }
    catch (Exception e) { return { foo: foo, filter: true, error: e } };
})
.Where(f => f.filter)
.Subscribe(f =>
{
    if (f.error != null) { handle error }
    else { handle f.foo }
});

其他方法包括使用
CatchException
Retry
。Rxx库中有一些成对的观测值,具有左/右通道和
类型,您可以使用它们在“左”流式传输良好数据,在“右”流式传输错误。

异常通常不好抛出。充其量,他们会引入一个类似于
goto
的语句,使代码的逻辑变得更难,最坏的情况下,他们会删除
AppDomain
。在Rx中应该没有什么不同。避免他们,除非,啊哼,发生特殊情况。我强烈不同意这一点,特别是考虑到他们在我们的工作项目中为我们工作得多么好,但谢谢你的评论。功能(和反应)方式,如中所述