subscribeOn和observeOn只应由最终订阅者调用吗?

12

Intro to Rx中的调度和线程一节指出:

使用SubscribeOn和ObserveOn应该只由最终订阅者调用。

它还指出,在UI应用程序中,通常是最终订阅者的表示层应该调用这些方法。

我想知道这个建议是否可靠,因为我在一些情况下发现这不太方便:

首先,我认为展示层不应该决定数据层产生的Observable在哪里被订阅。在我看来,展示层应该不知道数据是来自数据库、REST API还是内存。因此,方便起见,数据层在返回Observable之前调用subscribeOn(),传递IO Scheduler或立即Scheduler作为方便。
如果展示层从某个服务或用例(反过来从数据层获取)获取Observable,并且该服务决定需要在某些计算Scheduler中处理流,那么为什么展示层要关心这一点呢?
那么一个最初来自UI的流怎么办,所以它需要在UI线程中订阅。然后它将被发送到某个服务去做一些工作,最后回到展示层在UI线程中观察。这将需要UI流subscribeOn() UI Scheduler,然后observeOn()其他Scheduler,最后observeOn() UI Scheduler。在这种情况下,能够只在最终订阅者中调用subscribeOn()observeOn()意味着该流只能在UI线程中处理。

有没有什么好的理由让我牺牲我的应用程序架构,忽略Rx通过仅通过最终订阅者调用这两个方法轻松切换线程的能力?

2个回答

3
  1. 表现层并不关心可观察对象来自哪里,但如果它可能会锁定 UI 线程,表现层就要采取预防措施以防止发生这种情况。这是一种幸福的无知,带有一层安全保障。

  2. 表现层并不在意,它只想确保自己不被阻塞。

  3. 如果流来自 UI 并且处理时间较长,则流的所有者应该对此进行认真考虑,并确保在非 UI 调度程序上处理流。然后,如果需要在 UI 线程上使用,UI 必须确保它返回到 UI 线程。如果处理速度很快,则没有关系。


我同意表示层不希望UI线程被锁定,但我认为决定其他层在哪里进行工作不是表示层的职责。此外,这迫使表示层选择使用哪个调度程序,但它并不知道工作的性质(计算、IO、其他?)。我对表示层应该关注其他层所做的事情有问题。另外,请问你所说的流的所有者是指谁? - sorianiv
@sorianiv - 我没有说“演示应该考虑其他层做什么的问题”。我确实说过UI需要注意自己创建任何长时间运行在UI上的observables。流的所有者是编写查询的程序员。 - Enigmativity

3

很高兴看到您已经阅读了这本书,并花时间挑战其中的一些指导。

我提供这些指导的原因是:

  1. 并发很难,有一个简单的规则可以帮助团队编写更好的代码。
  2. 并发很难,将所有并发问题集中在一个地方可以更好地理解您的堆栈/分层模型,并且应该简化测试。引入并发问题的层数越多,应用程序就越糟糕。
  3. 阻塞UI线程不是好消息。尽快离开UI线程,然后尽可能晚地延迟任何数据处理回UI线程是更可取的。这个模式旨在实现这个目标。

显然这些是我的个人意见,但我看到这些简单的指导方针帮助了数十个项目优化代码,减少了代码库,提高了可测试性,在许多情况下也极大地提高了性能。

遗憾的是,由于大多数项目受到保密协议(NDA)的保护,很难汇总这些项目的案例研究。

我很想看看它对您的工作的影响,或者您如何应用其他的模式。


并发编程很难,然而制定好的规则不仅要简单易懂。例如,你提出的另一个建议“避免使用阻塞调用”有实例支持,我除了在测试代码之外没有异议。我认为你的读者会从subscribeOnobserveOn问题的好示例中获益匪浅。_DispatcherScheduler_章节中的示例并不令人满意,因为它通过其他方式引入了并发问题。 - sorianiv
你提出的其他观点都很好,但我还没有被说服它们超过了利用 Rx 简化并发的好处。我更喜欢让知道自己工作性质的组件决定是否需要切换执行器以及切换到哪个执行器。让表示层强制执行 UI 线程的非阻塞会迫使其也选择特定的执行器,这样就过多地了解了其他组件如何完成其工作。 - sorianiv
我目前尝试的是“如果您知道Observable必须在特定线程中订阅,或者如果进行了某些计算或IO工作,请在传递流之前调用subscribeOn。如果您即将进行某些计算或IO工作,或者如果您是最终订阅者并且观察者需要特定的线程,则在接收到的流上调用observeOn。” 我还没有足够的经验来完全推荐它。 - sorianiv
我理解你的最后一点,这似乎是合理的。如果你是独自工作或者在一个小团队中拥有一个小的代码库,并且你可以做出这些决策,那么这可能是一个不错的选择。然而,通常情况下,在更大的代码库中,你可能不知道底层流是否会执行任何IO或重计算。由于分层和缓存引入,我发现最初正确的假设可能随着时间的推移变得不正确。无论如何 - 已提出问题以创建示例 - https://github.com/LeeCampbell/RxCookbook/issues/26 - Lee Campbell
关于评论2的问题,理想情况下,您已经将调度程序抽象到某个提供程序/服务后面。我发现在大多数UI应用程序中,我们只需要暴露2个调度程序DispatcherBackground(它们委托给相关平台调度程序,例如TaskPoolThreadPool)。该提供程序还可以提供一种获取专用EventLoopScheduler以执行特定IO操作的方法。 - Lee Campbell
我决定接受这个答案,主要基于你的回答中的第1点和第2点。我尝试了评论中描述的方法,但在大型项目中似乎会导致错误。关于调度程序提供者的建议很好。 - sorianiv

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接