简体   繁体   English

如果我在Observable的Subscribe回调中引发异常,应该怎么办?

[英]What should happen if I throw an exception in my Subscribe callback for an Observable?

I'm using the latest Reactive Extensions and have run into a design issue: 我正在使用最新的Reactive Extensions并遇到了一个设计问题:

What should happen if I throw an exception from the delegate I pass into Subscribe? 如果我从委托中抛出异常,我会进入Subscribe会发生什么?

Via source stepping, I have found: 通过源步骤,我发现:

  • Subjects will ignore the exception. 主题将忽略该异常。
  • Operators derived from Producer (such as Where ) dispose the subscription as the exception passes through them. 从Producer派生的运算符(例如Where )在异常通过它们时处理订阅。

So of course I've been finding that anywhere I pass an observable through a standard RX operator, any exception causes my events to stop right there due to the disposal. 因此,我当然发现在通过标准RX运算符传递可观察对象的任何地方,任何异常都会由于处置而导致我的事件就此停止。 At least, unless I re-subscribe. 至少,除非我重新订阅。

This is leading me to question my design. 这让我质疑我的设计。 Is it a bad idea to throw exceptions from my delegates? 从我的代表那里抛出异常是一个坏主意吗? Clearly the RX team thinks so. 显然,RX团队是这么认为的。 (Though I'd question whether silently disposing the "bad" subscription is the right way to go.) (虽然我怀疑是否正确地处理“坏”订阅是正确的方法。)

Looking at my design, though, I don't see why it's a problem. 但是,看看我的设计,我不明白为什么这是一个问题。 I have some protected operations going on, I start firing some OnNext's to notify listeners (we have fully switched over from old skool .NET events to Observables), if anything goes wrong in there, it will throw up the stack until it hits a handler. 我正在进行一些受保护的操作,我开始触发一些OnNext来通知侦听器(我们已经从旧的skool .NET事件完全切换到了Observables),如果那里发生任何错误,它将抛出堆栈,直到它到达处理程序为止。 In my case, the handler rolls back a transaction it's working on, which also notifies the listeners of the rollback. 在我的情况下,处理程序会回滚正在处理的事务,这还会通知监听者回滚。 It's all exception-safe, works fine. 都是异常安全的,一切正常。 At least, it works fine without the Dispose going on in the Where operator's Producer base. 至少,如果在“ Where”操作员的“生产者”基地中不进行“处置”,则它可以正常工作。

Going a little further.. is it inconsistent that Subject and peers don't do this behavior? 更进一步..主题和同行不做这种行为是不一致的吗? And for our own ISubjects and observable operators that we've written here, should we be doing this same dispose-on-exception behavior? 对于我们在此处编写的ISubject和可观察的运算符,我们是否应该执行相同的异常处理行为?

I'm looking forward to any insights! 我期待着任何见解!

Throw exceptions when there are problems. 有问题时抛出异常。 Catch them when you think you can deal with the problems they indicate. 当您认为可以解决它们指出的问题时,请抓住它们。 Language support for exception handling assumes you have a function call stack and so the exception just climbs the stack looking for a handler. 异常处理的语言支持假设您有一个函数调用堆栈,因此异常只是爬上堆栈寻找处理程序。

But remember when using Rx, the processing model has been turned sideways. 但是请记住,使用Rx时,处理模型已经转向了侧面。 You no longer have a deep function call stack with the source (calling code) at the top and the observer (called code) at the bottom. 您不再拥有深层函数调用堆栈,其顶部是源(调用代码),底部是观察者(称为代码)。 So you cannot rely on the language to do the right thing within your Rx stream. 因此,您不能依赖语言在Rx流中做正确的事情。

If your callback throws an exception then Rx tends to capture that exception and pass it down through the OnError channel of the observable. 如果你的回调抛出一个异常,那么Rx倾向于捕获该异常并将其传递给observable的OnError通道。 If you do not supply an OnError handler when you subscribe, then Rx tends to raise the exception on a background thread, generating an unhandled exception in your application. 如果在订阅时未提供OnError处理程序,则Rx会在后台线程上引发异常,从而在应用程序中生成未处理的异常。

Rx does not notify the datasource that something downstream has had an exception. RX 不会通知下游的东西已经有一个异常的数据源。 This is because the datasource is completely isolated from the data consumers. 这是因为数据源与数据使用者完全隔离。 It might not be on the same process or even the same machine or written in the same language. 它可能不在同一个进程或甚至是同一台机器上,也不是用同一种语言编写。 That is why the Subject does not do anything. 这就是为什么Subject什么都不做的原因。 The Subject is acting as the data source in this case and doesn't really care about what observers do. 在这种情况下, Subject充当数据源,并不真正关心观察者的行为。

As you've noted, uncaught exceptions will cause Rx to unsubscribe your observer from the observable by default. 正如您所指出的那样,未捕获的异常将导致Rx默认情况下从观察者中取消订阅观察者。 This is the fail-fast philosophy and is the only safe assumption Rx can make. 这是快速失败的理念,是Rx可以做出的唯一安全假设。 If your observer raised an exception then something must be wrong and we should not give it more data by default. 如果你的观察者提出异常,那么一定是错的,我们不应该默认给它更多的数据。 Rx provides mechanisms that let you handle exceptions in ways that do not necessarily unsubscribe from the observable. Rx提供的机制允许您以不一定取消订阅observable的方式处理异常。

One way is to turn your exceptions into data: 一种方法是将异常转换为数据:

// 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 }
});

Other ways involve using CatchException or Retry . 其他方法涉及使用CatchExceptionRetry The Rxx library has some paired observables with left/right channels and an Either type that you can use to stream your good data on the "left" and your errors on the "right". Rxx库有一些带左/右声道的配对可观察对象和一个可用于在“左”上传输好数据的Either类型和在“右”上传输错误。

Ok, I found my answer in the source. 好的,我在源代码中找到了答案。 Will simply paste it here for posterity. 只需将其粘贴在此处以供后代使用。

// 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.

So the answer is: yes, it's a bad idea to throw exceptions in OnNext. 所以答案是:是的,在OnNext中抛出异常是个坏主意。 In general. 一般来说。 In my specific case I know it's ok, so I am going to find another way. 在我的具体情况下,我知道它没关系,所以我将找到另一种方式。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM