ObserveOn和SubscribeOn - 正在完成工作的地方

时间:2013-12-08 09:41:43

标签: c# .net system.reactive

基于阅读此问题:What's the difference between SubscribeOn and ObserveOn

ObserveOn设置执行Subscribe处理程序中代码的位置:

stream.Subscribe(_ => { // this code here });

SubscribeOn方法设置完成流设置的线程。

我了解如果没有明确设置,则使用TaskPool。

现在我的问题是,让我说我做这样的事情:

Observable.Interval(new Timespan(0, 0, 1)).Where(t => predicate(t)).SelectMany(t => lots_of(t)).ObserveOnDispatcher().Subscribe(t => some_action(t));

鉴于调度员正在执行Wherepredicate SelectManylots_of some_action在哪里执行?

3 个答案:

答案 0 :(得分:163)

有很多关于SubscribeOnObserveOn的误导性信息。

摘要

  • SubscribeOn 拦截对 IObservable<T> 的单一方法的调用,即Subscribe,并调用DisposeIDisposable返回的Subscribe句柄上。
  • ObserveOn 拦截对 IObserver<T> 方法的调用,这些方法为OnNextOnCompleted&amp; OnError
  • 这两种方法都会导致在指定的调度程序上进行相应的调用。

分析&amp;示范

声明

  

ObserveOn设置Subscribe处理程序中代码的位置   执行:

比帮助更令人困惑。你所指的是&#34;订阅处理程序&#34;实际上是一个OnNext处理程序。请注意,Subscribe IObservable方法接受IObserver OnNextOnCompletedOnError方法,但它是提供的扩展方法方便重载接受lambdas并为你构建IObserver实现。

让我适应这个词;我想到了&#34;订阅处理程序&#34;作为可观察中的代码,在调用Subscribe时调用。通过这种方式,上面的描述更类似于SubscribeOn的目的。

SubscribeOn

SubscribeOn导致observable的Subscribe方法在指定的调度程序或上下文上异步执行。当你不想从你正在运行的任何线程上的一个observable上调用Subscribe方法时,你可以使用它 - 通常是因为它可以长时间运行并且你不想阻止它调用线程。

当你调用Subscribe时,你正在调用一个可观察者,它可能是一长串可观察者的一部分。它只是SubscribeOn应用于它的效果的可观察量。现在可能的情况是,链中的所有可观察量都将立即订阅并在同一个线程上 - 但事实并非如此。例如,考虑Concat - 只有在前一个流完成后才订阅每个连续流,通常这将在前一个流称为OnCompleted的任何线程上发生。

所以SubscribeOn位于您对Subscribe的调用和您正在订阅的观察之间,拦截调用并使其异步。

它还会影响订阅的处理。 Subscribe会返回IDisposable句柄,用于取消订阅。 SubscribeOn确保在提供的调度程序上安排对Dispose的调用。

在尝试理解SubscribeOn所做的事情时,一个常见的混淆点是,观察者的Subscribe处理程序可能会调用OnNextOnCompleted或{{1}在同一个线程上。但是,其目的不是影响这些调用。在OnError方法返回之前完成流的情况并不少见。例如,Subscribe就是这样做的。我们来看看。

如果您使用我编写的Spy方法,并运行以下代码:

Observable.Return

你得到这个输出(线程ID可能会有所不同):

Console.WriteLine("Calling from Thread: " + Thread.CurrentThread.ManagedThreadId);
var source = Observable.Return(1).Spy("Return");
source.Subscribe();
Console.WriteLine("Subscribe returned");

您可以看到整个订阅处理程序在同一个线程上运行,并在返回之前完成。

让我们使用Calling from Thread: 1 Return: Observable obtained on Thread: 1 Return: Subscribed to on Thread: 1 Return: OnNext(1) on Thread: 1 Return: OnCompleted() on Thread: 1 Return: Subscription completed. Subscribe returned 异步运行它。我们将窥探SubscribeOn观察者和Return观察者:

SubscribeOn

此输出(由我添加的行号):

Console.WriteLine("Calling from Thread: " + Thread.CurrentThread.ManagedThreadId);
var source = Observable.Return(1).Spy("Return");
source.SubscribeOn(Scheduler.Default).Spy("SubscribeOn").Subscribe();
Console.WriteLine("Subscribe returned");

01 - 主要方法在线程1上运行。

02 - 在调用线程上评估01 Calling from Thread: 1 02 Return: Observable obtained on Thread: 1 03 SubscribeOn: Observable obtained on Thread: 1 04 SubscribeOn: Subscribed to on Thread: 1 05 SubscribeOn: Subscription completed. 06 Subscribe returned 07 Return: Subscribed to on Thread: 2 08 Return: OnNext(1) on Thread: 2 09 SubscribeOn: OnNext(1) on Thread: 2 10 Return: OnCompleted() on Thread: 2 11 SubscribeOn: OnCompleted() on Thread: 2 12 Return: Subscription completed. observable。我们刚刚收到Return,还没有订阅。

03 - 在调用线程上评估IObservable observable。

04 - 现在我们最终调用SubscribeOn的{​​{1}}方法。

05 - Subscribe方法异步完成...

06 - ...并且线程1返回主方法。 这是SubscribeOn在行动中的效果!

07 - 同时,SubscribeOn在默认调度程序上安排了对Subscribe的调用。这是在线程2上收到的。

08 - 正如SubscribeOn所做的那样,它会在Return主题上调用Return ...

09 - 而OnNext现在只是一个过程。

10,11 - Subscribe

相同

12 - 最后,SubscribeOn订阅处理程序已完成。

希望能够清除OnCompleted

的目的和效果

ObserveOn

如果您认为Return是将SubscribeOn方法传递给另一个线程的SubscribeOn方法的拦截器,那么Subscribe执行相同的工作,但{{1} }},ObserveOnOnNext来电。

回想一下我们原来的例子:

OnCompleted

这给出了这个输出:

OnError

现在让我们改变它以使用Console.WriteLine("Calling from Thread: " + Thread.CurrentThread.ManagedThreadId); var source = Observable.Return(1).Spy("Return"); source.Subscribe(); Console.WriteLine("Subscribe returned");

Calling from Thread: 1
Return: Observable obtained on Thread: 1
Return: Subscribed to on Thread: 1
Return: OnNext(1) on Thread: 1
Return: OnCompleted() on Thread: 1
Return: Subscription completed.
Subscribe returned

我们得到以下输出:

ObserveOn

01 - 主要方法在线程1上运行。

02 - 和以前一样,在调用线程上评估Console.WriteLine("Calling from Thread: " + Thread.CurrentThread.ManagedThreadId); var source = Observable.Return(1).Spy("Return"); source.ObserveOn(Scheduler.Default).Spy("ObserveOn").Subscribe(); Console.WriteLine("Subscribe returned"); observable。我们刚刚收到01 Calling from Thread: 1 02 Return: Observable obtained on Thread: 1 03 ObserveOn: Observable obtained on Thread: 1 04 ObserveOn: Subscribed to on Thread: 1 05 Return: Subscribed to on Thread: 1 06 Return: OnNext(1) on Thread: 1 07 ObserveOn: OnNext(1) on Thread: 2 08 Return: OnCompleted() on Thread: 1 09 Return: Subscription completed. 10 ObserveOn: Subscription completed. 11 Subscribe returned 12 ObserveOn: OnCompleted() on Thread: 2 ,还没有订阅。

03 - Return observable也在调用线程上进行评估。

04 - 现在我们再次在调用线程上订阅IObservable可观察的...

05 - ...然后将调用传递给ObserveOn observable。

06 - 现在ObserveOn在其Return处理程序中调用Return

07 - 以下是OnNext的效果。我们可以看到Subscribe是在线程2上异步安排的。

08 - 同时ObserveOn在线程1上调用OnNext ...

09 - Return订阅处理程序完成...

10 - 然后OnCompleted的订阅处理程序......

也是如此

11 - 所以控制权返回主方法

12 - 与此同时,Return已经将ObserveOn的{​​{1}}传递给线程2.这可能发生在09-11期间的任何时间,因为它是异步运行。恰好如此,它现在终于被称为。

典型的用例是什么?

当你需要ObserveOn到一个长时间运行的observable并希望尽快离开调度程序线程时,你经常会看到GUI中使用的Return - 也许是因为你知道&# 39;其中一个可以在订阅处理程序中完成所有工作的可观察对象。将它应用于可观察链的末尾,因为这是您订阅时调用的第一个observable。

如果要确保将OnCompletedSubscribeOnSubscribe调用编组回调度程序线程,您通常会在GUI中看到ObserveOn。将它应用于可观察链的末端,以便尽可能晚地过渡。

希望你能看到问题的答案是OnNextOnCompletedOnError执行的线程没有任何影响 - 这一切都取决于什么线程 stream 正在调用它们!将在调用线程上调用stream的订阅处理程序,但在不知道如何实现ObserveOnDispatcher的情况下,无法确定WhereSelectMany的运行位置。< / p>

生命周期比订阅电话更长的可观察者

到目前为止,我们一直专注于WhereSelectManystream处理程序中完成其流。这不是非典型的,但是流量比Observable.Return处理程序更长,这同样很常见。以Return为例:

Subscribe

返回以下内容:

Subscribe

您可以清楚地看到订阅完成,然后Observable.TimerConsole.WriteLine("Calling from Thread: " + Thread.CurrentThread.ManagedThreadId); var source = Observable.Timer(TimeSpan.FromSeconds(1)).Spy("Timer"); source.Subscribe(); Console.WriteLine("Subscribe returned"); 稍后在另一个主题上调用。

请注意,Calling from Thread: 1 Timer: Observable obtained on Thread: 1 Timer: Subscribed to on Thread: 1 Timer: Subscription completed. Subscribe returned Timer: OnNext(0) on Thread: 2 Timer: OnCompleted() on Thread: 2 OnNext的任何组合都不会有任何影响哪个线程或调度程序OnCompleted选择调用SubscribeOnObserveOn on。

当然,您可以使用Timer来确定OnNext主题:

OnCompleted

(我故意更改为SubscribeOn,以防止Subscribe发生混淆以获得与Console.WriteLine("Calling from Thread: " + Thread.CurrentThread.ManagedThreadId); var source = Observable.Timer(TimeSpan.FromSeconds(1)).Spy("Timer"); source.SubscribeOn(NewThreadScheduler.Default).Spy("SubscribeOn").Subscribe(); Console.WriteLine("Subscribe returned"); 相同的线程池线程

,并提供:

NewThreadScheduler

在这里你可以清楚地看到线程(1)上的主线程在其Timer调用之后返回,但SubscribeOn订阅获得了自己的线程(2),但是Calling from Thread: 1 Timer: Observable obtained on Thread: 1 SubscribeOn: Observable obtained on Thread: 1 SubscribeOn: Subscribed to on Thread: 1 SubscribeOn: Subscription completed. Subscribe returned Timer: Subscribed to on Thread: 2 Timer: Subscription completed. Timer: OnNext(0) on Thread: 3 SubscribeOn: OnNext(0) on Thread: 3 Timer: OnCompleted() on Thread: 3 SubscribeOn: OnCompleted() on Thread: 3 Subscribe调用在线程(3)上运行。

现在Timer,让我们将代码更改为(对于代码中的后续代码,请使用nuget包rx-wpf):

OnNext

这段代码有点不同。第一行确保我们有一个调度程序,我们也引入了OnCompleted - 这就像ObserveOn一样,除了它指定我们应该使用任何线程的var dispatcher = Dispatcher.CurrentDispatcher; Console.WriteLine("Calling from Thread: " + Thread.CurrentThread.ManagedThreadId); var source = Observable.Timer(TimeSpan.FromSeconds(1)).Spy("Timer"); source.ObserveOnDispatcher().Spy("ObserveOn").Subscribe(); Console.WriteLine("Subscribe returned"); {{ 1}}在上进行评估。

此代码提供以下输出:

ObserveOnDispatcher

请注意,调度程序(和主线程)是线程1. ObserveOn仍然在其选择的线程(2)上调用DispatcherSchedulerObserveOnDispatcher - 但是{{1将调用回调到调度程序线程thread(1)。

另请注意,如果我们要阻止调度程序线程(例如Calling from Thread: 1 Timer: Observable obtained on Thread: 1 ObserveOn: Observable obtained on Thread: 1 ObserveOn: Subscribed to on Thread: 1 Timer: Subscribed to on Thread: 1 Timer: Subscription completed. ObserveOn: Subscription completed. Subscribe returned Timer: OnNext(0) on Thread: 2 ObserveOn: OnNext(0) on Thread: 1 Timer: OnCompleted() on Thread: 2 ObserveOn: OnCompleted() on Thread: 1 ),您会看到Timer会阻塞(此代码在LINQPad主方法中效果最佳):

OnNext

你会看到这样的输出:

OnCompleted

通过ObserveOnDispatcher的来电只有在Thread.Sleep运行后才能退出。

要点

有用的是要记住,Reactive Extensions本质上是一个自由线程库,并试图尽可能地保持它运行的线程 - 你必须故意干扰ObserveOnDispatchervar dispatcher = Dispatcher.CurrentDispatcher; Console.WriteLine("Calling from Thread: " + Thread.CurrentThread.ManagedThreadId); var source = Observable.Timer(TimeSpan.FromSeconds(1)).Spy("Timer"); source.ObserveOnDispatcher().Spy("ObserveOn").Subscribe(); Console.WriteLine("Subscribe returned"); Console.WriteLine("Blocking the dispatcher"); Thread.Sleep(2000); Console.WriteLine("Unblocked"); 并将特定的调度程序传递给接受它们的操作员来改变它。

观察者的消费者无法控制其内部行为 - Calling from Thread: 1 Timer: Observable obtained on Thread: 1 ObserveOn: Observable obtained on Thread: 1 ObserveOn: Subscribed to on Thread: 1 Timer: Subscribed to on Thread: 1 Timer: Subscription completed. ObserveOn: Subscription completed. Subscribe returned Blocking the dispatcher Timer: OnNext(0) on Thread: 2 Timer: OnCompleted() on Thread: 2 Unblocked ObserveOn: OnNext(0) on Thread: 1 ObserveOn: OnCompleted() on Thread: 1 ObserveOnDispatcher decorators包裹表面区域观察者和观察者来跨越线程编组调用。希望这些例子清楚地说明了这一点。

答案 1 :(得分:13)

我发现詹姆斯的回答非常明确和全面。然而,尽管如此,我仍然发现自己必须解释这些差异。

因此,我创建了一个非常简单/愚蠢的示例,它允许我以图形方式演示调用哪些调度程序。我创建了一个立即执行操作的类MyScheduler,但会更改控制台颜色。

SubscribeOn调度程序的文本输出以红色输出,ObserveOn调度程序的输出以蓝色输出。

using System;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Linq;

namespace SchedulerExample
{

    class Program
    {
        static void Main(string[] args)
        {
            var mydata = new[] {"A", "B", "C", "D", "E"};
            var observable = Observable.Create<string>(observer =>
                                            {
                                                Console.WriteLine("Observable.Create");
                                                return mydata.ToObservable().
                                                    Subscribe(observer);
                                            });

            observable.
                SubscribeOn(new MyScheduler(ConsoleColor.Red)).
                ObserveOn(new MyScheduler(ConsoleColor.Blue)).
                Subscribe(s => Console.WriteLine("OnNext {0}", s));

            Console.ReadKey();
        }
    }
}

输出:

scheduler

参考MyScheduler(不适合实际使用):

using System;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;

namespace SchedulerExample
{
    class MyScheduler : IScheduler
    {
        private readonly ConsoleColor _colour;

        public MyScheduler(ConsoleColor colour)
        {
            _colour = colour;
        }

        public IDisposable Schedule<TState>(TState state, Func<IScheduler, TState, IDisposable> action)
        {
            return Execute(state, action);
        }

        private IDisposable Execute<TState>(TState state, Func<IScheduler, TState, IDisposable> action)
        {
            var tmp = Console.ForegroundColor;
            Console.ForegroundColor = _colour;
            action(this, state);
            Console.ForegroundColor = tmp;
            return Disposable.Empty;
        }

        public IDisposable Schedule<TState>(TState state, TimeSpan dueTime, Func<IScheduler, TState, IDisposable> action)
        {
            throw new NotImplementedException();
        }

        public IDisposable Schedule<TState>(TState state, DateTimeOffset dueTime, Func<IScheduler, TState, IDisposable> action)
        {
            throw new NotImplementedException();
        }

        public DateTimeOffset Now
        {
            get { return DateTime.UtcNow; }
        }
    }
}

答案 2 :(得分:0)

我经常误认为.SubcribeOn用于设置正在执行.Subscribe内部代码的线程。但要记住,只要认为发布和订阅必须像阴阳一样。要设置执行Subscribe's code的位置,请使用ObserveOn。设置Observable's code执行的使用位置SubscribeOn。或者总结一句咒语:where-whatSubscribe-ObserveObserve-Subscribe