ObserveOn и SubscribeOn-где выполняется работа
основываясь на чтении этого вопроса:в чем разница между SubscribeOn и 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
выполняется ли диспетчер?
3 ответов
существует много вводящей в заблуждение информации о SubscribeOn
и ObserveOn
.
резюме
-
SubscribeOn
перехватывает вызовы одного методаIObservable<T>
, которая составляетSubscribe
, иDispose
наIDisposable
дескриптор, возвращенныйSubscribe
. -
ObserveOn
перехватывает вызовы методовIObserver<T>
, которыеOnNext
,OnCompleted
&OnError
. - оба метода вызывают соответствующие вызовы для указанного планировщика.
Анализ И Демонстрации
заявление
ObserveOn устанавливает, где код в обработчике подписки исполнено:
более запутанно, чем полезно. То, что вы называете "обработчиком подписки", на самом деле является OnNext
обработчик. Помните, что Subscribe
метод IObservable
принимает IObserver
и OnNext
, OnCompleted
и OnError
методы, но это методы расширения, которые обеспечивают удобные перегрузки, которые принимают лямбды и создают IObserver
реализация для вас.
позвольте мне присвоить термин, хотя; я думаю, что "обработчик подписки" является кодом в обозримой, который вызывается, когда Subscribe
называется. Таким образом, приведенное выше описание более близко напоминает цель SubscribeOn
.
SubscribeOn
SubscribeOn
вызывает Subscribe
метод наблюдаемого, который будет выполняться асинхронно в указанном планировщике или контексте. Вы используете его, когда не хотите вызывать Subscribe
метод на наблюдаемом из любого потока, на котором вы работаете-обычно потому, что он может быть длительным, и вы не хотите блокировать вызывающий поток.
когда вы называете Subscribe
, вы вызываете наблюдаемое, которое может быть частью длинная цепь наблюдаемых объектов. Это только наблюдаемое, что SubscribeOn
применяется к тому, что он влияет. Теперь может быть так, что все наблюдаемые в цепочке будут подписаны сразу и в одном потоке - но это не обязательно должно быть так. Думал о Concat
например-это подписывается только на каждый последующий поток после завершения предыдущего потока, и обычно это будет происходить в любом потоке, который предыдущий поток называется OnCompleted
from.
так SubscribeOn
сидит между вашим звонком Subscribe
и наблюдаемый, на который вы подписываетесь, перехватывая вызов и делая его асинхронным.
это также влияет на удаление подписок. Subscribe
возвращает IDisposable
дескриптор, который используется для отмены подписки. SubscribeOn
обеспечивает звонки Dispose
запланированы на поставленный планировщик.
общая точка путаницы при попытке понять, что SubscribeOn
делает это Subscribe
обработчик наблюдаемого мая ну звоните OnNext
, OnCompleted
или OnError
по этой же теме. Однако его цель не заключается в том, чтобы повлиять на эти вызовы. Это не редкость для потока, чтобы завершить до Subscribe
возвращает метод. Observable.Return
это, например. Давайте посмотрим.
если вы используете шпион метод, который я написал, и выполните следующий код:
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
вы можете видеть, что весь обработчик подписки выполняется в том же потоке и завершается перед возвратом.
давайте!--16--> чтобы запустить это асинхронно. Мы будем шпионить за обоими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 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.
01 - основной метод выполняется в потоке 1.
02-the Return
observable вычисляется в вызывающем потоке. Мы только что получили IObservable
здесь, пока ничего не подписано.
03-the SubscribeOn
observable вычисляется в вызывающем потоке.
04 - теперь, наконец, мы называем Subscribe
метод SubscribeOn
.
05-The Subscribe
метод выполняется асинхронно...
06 - ... и нить 1 возвращает метод main. это эффект подписки в действии!
07 - между тем, SubscribeOn
запланированный вызов планировщика по умолчанию Return
. Здесь он получен по потоку 2.
08 - и как Return
делает, он называет OnNext
на Subscribe
нить...
09 - и SubscribeOn
теперь это просто проход.
10,11-то же самое для OnCompleted
12-и последнее из всех Return
обработчик подписки делается.
надеюсь, что проясняет цель и эффект SubscribeOn
!
ObserveOn
если вы думаете о SubscribeOn
как перехватчик для Subscribe
метод, который передает вызов в другом потоке, то 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
:
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");
мы получаем следующий результат:
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
01-основной метод работает на потоке 1.
02 - как прежде,Return
observable вычисляется в вызывающем потоке. Мы только что получили IObservable
здесь еще ничего не подписано.
03-The ObserveOn
observable также вычисляется в вызывающем потоке.
04-теперь мы подписываемся, снова на вызывающий поток, сначала на ObserveOn
наблюдаема...
05 - ... который затем передает вызов через Return
наблюдаема.
06 - теперь Return
звонки OnNext
в своем Subscribe
обработчик.
07 - вот эффект ObserveOn
. мы видим, что OnNext
запланировано асинхронно в потоке 2.
08 - а Return
звонки OnCompleted
в потоке 1...
09 - и Return
завершает обработчик подписки...
10-и тогда так делает 'подписки С...
11-так управление возвращено к основному метод
12 - А, ObserveOn
осуществляла Return
' s OnCompleted
вызовите это в поток 2. Это могло произойти в любое время в течение 09-11, потому что он выполняется асинхронно. Просто так получилось, что это наконец-то называется сейчас.
каковы типичные случаи использования?
вы будете чаще всего видеть SubscribeOn
используется в GUI, когда вам нужно Subscribe
для длительного наблюдения и хотите как можно скорее выйти из потока диспетчера-возможно потому что вы знаете, что это один из тех наблюдаемых объектов, который выполняет всю работу в обработчике подписки. Примените его в конце наблюдаемой цепочки, потому что это первый наблюдаемый вызов, когда вы подписываетесь.
вы будете чаще всего видеть ObserveOn
используется в GUI, когда вы хотите обеспечить OnNext
, OnCompleted
и OnError
вызовы сортируются обратно в поток диспетчера. Примените его в конце наблюдаемой цепи для перехода назад до тех пор, пока вероятный.
надеюсь, вы видите, что ответ на ваш вопрос заключается в том, что ObserveOnDispatcher
не будет иметь никакого значения для потоков, которые Where
и SelectMany
выполняются на - все зависит от того, какой поток поток вызывает их! обработчик подписки stream будет вызываться в вызывающем потоке, но невозможно сказать, где Where
и SelectMany
будет работать, не зная, как есть.
наблюдаемых с жизни, которые переживут вызов Subscribe
до сих пор мы смотрели исключительно на 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
будет никакого эффекта на каком потоке или планировщике Timer
выбирает для вызова OnNext
и OnCompleted
on.
конечно, вы можете использовать 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
здесь, чтобы предотвратить путаницу в случае Timer
происходит, чтобы получить тот же поток пула потоков, что и SubscribeOn
)
даем:
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
здесь вы можете ясно видеть основной поток в потоке (1), возвращающийся после его Subscribe
звонит, но Timer
подписка получает свой собственный поток (2), но OnNext
и OnCompleted
вызовы, выполняемые в потоке (3).
теперь ObserveOn
, Давайте изменим код На (для тех, кто следует в коде, используйте пакет nuget rx-wpf):
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");
этот код немного отличается. Первая строка гарантирует, что у нас есть диспетчер, и мы также приносим ObserveOnDispatcher
- это так же, как ObserveOn
, за исключением того, что он указывает, что мы должны использовать DispatcherScheduler
какой-нить ObserveOnDispatcher
оценивается в.
этот код дает следующие результаты:
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
обратите внимание, что диспетчер (и основной поток) являются потоком 1. Timer
все еще звонит OnNext
и OnCompleted
на нить своего выбора (2) - но ObserveOnDispatcher
маршалинг вызывает назад на поток диспетчера, поток (1).
также обратите внимание, что если мы должны были заблокировать поток диспетчера (скажем,Thread.Sleep
) вы увидите, что ObserveOnDispatcher
будет блокировать (этот код лучше всего работает внутри основного метода LINQPad):
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
только в состоянии выйти один раз Sleep
запустить.
ключевые моменты
полезно иметь в виду это реактивные расширения по существу являются библиотекой с свободной резьбой и пытаются быть как можно более ленивыми о том, какой поток он работает - вы должны намеренно вмешиваться в ObserveOn
, SubscribeOn
и передача определенных планировщиков операторам, которые принимают их, чтобы изменить это.
нет ничего, что потребитель наблюдаемого может сделать, чтобы контролировать то, что он делает внутри - ObserveOn
и SubscribeOn
are декораторы которые охватывают площадь поверхности наблюдателей и наблюдаемых маршалировать вызовы по потокам. Надеюсь, эти примеры прояснили это.
я нашел ответ Джеймса очень ясным и всеобъемлющим. Однако, несмотря на это, мне все еще приходится объяснять различия.
поэтому я создал очень простой / глупый пример, который позволяет мне графически продемонстрировать, какие вещи планировщиков вызываются. Я создал класс 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; }
}
}
}
Я часто ошибаюсь, что .SubcribeOn
используется для установки потока, где код внутри .Subscribe
выполняется. Но чтобы помнить, просто подумайте, что публикация и подписка должны быть парой, как инь-ян. Установить, где Subscribe's code
выполняется использовать ObserveOn
. Установить, где Observable's code
казнить использовать SubscribeOn
. Или в резюме заклинания:where-what
,Subscribe-Observe
,Observe-Subscribe
.