基于阅读此问题: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));
鉴于调度员正在执行Where
,predicate
SelectMany
和lots_of
some_action
在哪里执行?
答案 0 :(得分:163)
有很多关于SubscribeOn
和ObserveOn
的误导性信息。
SubscribeOn
拦截对 IObservable<T>
的单一方法的调用,即Subscribe
,并调用Dispose
在IDisposable
返回的Subscribe
句柄上。ObserveOn
拦截对 IObserver<T>
方法的调用,这些方法为OnNext
,OnCompleted
&amp; OnError
。声明
ObserveOn设置Subscribe处理程序中代码的位置 执行:
比帮助更令人困惑。你所指的是&#34;订阅处理程序&#34;实际上是一个OnNext
处理程序。请注意,Subscribe
IObservable
方法接受IObserver
OnNext
,OnCompleted
和OnError
方法,但它是提供的扩展方法方便重载接受lambdas并为你构建IObserver
实现。
让我适应这个词;我想到了&#34;订阅处理程序&#34;作为可观察中的代码,在调用Subscribe
时调用。通过这种方式,上面的描述更类似于SubscribeOn
的目的。
SubscribeOn
导致observable的Subscribe
方法在指定的调度程序或上下文上异步执行。当你不想从你正在运行的任何线程上的一个observable上调用Subscribe
方法时,你可以使用它 - 通常是因为它可以长时间运行并且你不想阻止它调用线程。
当你调用Subscribe
时,你正在调用一个可观察者,它可能是一长串可观察者的一部分。它只是SubscribeOn
应用于它的效果的可观察量。现在可能的情况是,链中的所有可观察量都将立即订阅并在同一个线程上 - 但事实并非如此。例如,考虑Concat
- 只有在前一个流完成后才订阅每个连续流,通常这将在前一个流称为OnCompleted
的任何线程上发生。
所以SubscribeOn
位于您对Subscribe
的调用和您正在订阅的观察之间,拦截调用并使其异步。
它还会影响订阅的处理。 Subscribe
会返回IDisposable
句柄,用于取消订阅。 SubscribeOn
确保在提供的调度程序上安排对Dispose
的调用。
在尝试理解SubscribeOn
所做的事情时,一个常见的混淆点是,观察者的Subscribe
处理程序可能会调用OnNext
,OnCompleted
或{{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
!
如果您认为Return
是将SubscribeOn
方法传递给另一个线程的SubscribeOn
方法的拦截器,那么Subscribe
执行相同的工作,但{{1} }},ObserveOn
和OnNext
来电。
回想一下我们原来的例子:
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。
如果要确保将OnCompleted
,SubscribeOn
和Subscribe
调用编组回调度程序线程,您通常会在GUI中看到ObserveOn
。将它应用于可观察链的末端,以便尽可能晚地过渡。
希望你能看到问题的答案是OnNext
对OnCompleted
和OnError
执行的线程没有任何影响 - 这一切都取决于什么线程 stream 正在调用它们!将在调用线程上调用stream的订阅处理程序,但在不知道如何实现ObserveOnDispatcher
的情况下,无法确定Where
和SelectMany
的运行位置。< / p>
到目前为止,我们一直专注于Where
。 SelectMany
在stream
处理程序中完成其流。这不是非典型的,但是流量比Observable.Return
处理程序更长,这同样很常见。以Return
为例:
Subscribe
返回以下内容:
Subscribe
您可以清楚地看到订阅完成,然后Observable.Timer
和Console.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
选择调用SubscribeOn
和ObserveOn
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)上调用DispatcherScheduler
和ObserveOnDispatcher
- 但是{{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本质上是一个自由线程库,并试图尽可能地保持它运行的线程 - 你必须故意干扰ObserveOnDispatcher
,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");
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();
}
}
}
输出:
参考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-what
,Subscribe-Observe
,Observe-Subscribe
。