C# 如果我在Subscribe回调中为一个可观察对象抛出异常,会发生什么?
我正在使用最新版本,遇到了一个设计问题: 如果我从传递给Subscribe的委托抛出异常,会发生什么 通过源代码步进,我发现:C# 如果我在Subscribe回调中为一个可观察对象抛出异常,会发生什么?,c#,.net,system.reactive,C#,.net,System.reactive,我正在使用最新版本,遇到了一个设计问题: 如果我从传递给Subscribe的委托抛出异常,会发生什么 通过源代码步进,我发现: 受试者将忽略例外情况 从Producer派生的运算符(例如其中)在异常通过时处理订阅 当然,我已经发现,无论我在哪里通过一个标准的RX操作符传递一个可观察到的,任何异常都会导致我的事件因为处理而停止。至少,除非我重新订阅 这让我怀疑我的设计。从我的代理中抛出异常是个坏主意吗?显然,RX团队也这么认为。(尽管我怀疑默默地处理“坏”订阅是否正确。) 但是,看看我的设计,
- 受试者将忽略例外情况
- 从Producer派生的运算符(例如
)在异常通过时处理订阅其中
我期待任何见解 好的,我在来源中找到了我的答案。只需将其粘贴在此处,以供子孙后代使用
// 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中应该没有什么不同。避免他们,除非,啊哼,发生特殊情况。我强烈不同意这一点,特别是考虑到他们在我们的工作项目中为我们工作得多么好,但谢谢你的评论。功能(和反应)方式,如中所述