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

вот результаты:

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

Я часто ошибаюсь, что .SubcribeOn используется для установки потока, где код внутри .Subscribe выполняется. Но чтобы помнить, просто подумайте, что публикация и подписка должны быть парой, как инь-ян. Установить, где Subscribe's code выполняется использовать ObserveOn. Установить, где Observable's code казнить использовать SubscribeOn. Или в резюме заклинания:where-what,Subscribe-Observe,Observe-Subscribe.