TPL против Reactive Framework - PullRequest
39 голосов
/ 30 марта 2010

Когда можно было бы использовать Rx вместо TPL или эти 2 каркаса ортогональны?

Из того, что я понимаю, Rx в первую очередь предназначен для предоставления абстракции над событиями и разрешения композиции, но также позволяет предоставлять абстракцию над асинхронными операциями. с использованием перегрузок Createxx и перегрузок Fromxxx и отмены путем удаления возвращенного IDisposable.

TPL также предоставляет абстракцию для операций с помощью задач и отмены.

Моя дилемма в том, когда использовать какие и для каких сценариев?

Ответы [ 5 ]

44 голосов
/ 30 марта 2010

Основная цель Rx - не предоставлять абстракцию над событиями. Это только один из его результатов. Его основная цель заключается в предоставлении составной модели push для коллекций.

Реактивная структура (Rx) основана на IObservable<T>, являющемся математическим двойником IEnumerable<T>. Поэтому вместо того, чтобы «извлекать» предметы из коллекции, используя IEnumerable<T>, мы можем «выталкивать» объекты к нам через IObservable<T>.

Конечно, когда мы на самом деле ищем наблюдаемые источники, такие вещи, как события и асинхронные операции, являются отличными кандидатами.

Реактивная структура, естественно, требует многопоточной модели, чтобы иметь возможность отслеживать источники наблюдаемых данных и управлять запросами и подписками. Для этого Rx активно использует TPL.

Так что, если вы используете Rx, вы неявно используете TPL.

Вы бы использовали TPL напрямую, если бы хотели прямого контроля над своими задачами.

Но если у вас есть источники данных, которые вы хотите наблюдать и выполнять запросы, я настоятельно рекомендую реактивную структуру.

24 голосов
/ 22 апреля 2010

Некоторые рекомендации, которым я хотел бы следовать:

  • Имею ли я дело с данными, которые я не создал. Данные, которые поступают, когда это нравится? Тогда RX.
  • Я инициирую вычисления и мне нужно управлять параллелизмом? Тогда TPL.
  • Управляю ли я несколькими результатами, и мне нужно выбирать из них в зависимости от времени? Тогда RX.
12 голосов
/ 21 января 2013

Обновление, декабрь 2016: Если у вас есть 30 минут, я рекомендую вам прочитать личный счет Джо Даффи, а не мои предположения. Я думаю, что мой анализ хорошо, но если вы нашли этот вопрос, я настоятельно рекомендую вам увидеть сообщение в блоге вместо этих ответов, потому что в дополнение к TPL против Rx.NET он также охватывает исследовательские проекты MS (Midori, Cosmos).

http://joeduffyblog.com/2016/11/30/15-years-of-concurrency/


Я думаю, что MS сделала большую ошибку из-за чрезмерного исправления после выхода .NET 2.0. Они представили множество различных API-интерфейсов управления параллелизмом одновременно из разных частей компании.

  • Стивен Тауб настаивал на том, чтобы потокобезопасные примитивы заменили Event (который начинался как Future<T> и превращался в Task<T>)
  • MS Research имела MIN-LINQ и реактивные расширения (Rx)
  • Аппаратное обеспечение / Встраиваемые устройства Cuntime робототехники (CCR)

Тем временем многие управляемые команды API пытались жить с APM и Threadpool.QueueUserWorkItem(), не зная, выиграет ли Тауб свою битву за доставку Future<T> / Task<T> в mscorlib.dll. В конце концов, похоже, что они хеджируются и отправляют как Task<T>, так и IObservable<T> в mscorlib, но не позволяют использовать другие API Rx (даже не ISubject<T>) в mscorlib. Я думаю, что это хеджирование привело к огромному количеству дублирования (более позднее) и потратило впустую усилия внутри и вне компании.

Для дублирования см .: Task против IObservable<Unit>, Task<T> против AsyncSubject<T>, Task.Run() против Observable.Start(). И это только верхушка айсберга. Но на более высоком уровне рассмотрим:

  • StreamInsight - потоки событий SQL, оптимизированные для собственного кода, но запросы событий, определенные с использованием синтаксиса LINQ
  • Поток данных TPL - построен на TPL, построен параллельно Rx, оптимизирован для настройки параллелизма потоков, плохо подходит для составления запросов
  • Rx - Удивительная выразительность, но таит в себе опасность. Смешивает «горячие» потоки с методами расширения IEnumerable, что означает, что вы очень легко блокируете навсегда (вызов First() в горячем потоке никогда не возвращается). Границы планирования (ограничение параллелизма) выполняются с помощью довольно странных SubscribeOn() методов расширения, которые странно неявны и их трудно понять правильно. Если вы начинаете изучать Rx, запаситесь долгим временем, чтобы изучить все подводные камни, которых следует избегать. Но Rx действительно единственный вариант, если вы создаете сложные потоки событий или вам нужна сложная фильтрация / запросы.

Я не думаю, что у Rx есть шанс на широкое распространение, пока MS не отправит ISubject<T> в mscorlib. Что печально, потому что Rx содержит несколько очень полезных конкретных (универсальных) типов, таких как TimeInterval<T> и Timestamped<T>, которые, я думаю, должны быть в Core / mscorlib, как Nullable<T>. Также System.Reactive.EventPattern<TEventArgs>.

12 голосов
/ 16 июля 2010

Мне нравятся пули Скотта В. Чтобы привести более конкретные примеры в RX очень хорошо отображает

  • потребляющие потоки
  • выполнение неблокирующей асинхронной работы, как веб-запросы.
  • потоковые события (либо .net-события, такие как движение мыши ИЛИ события типа сообщения служебной шины)
  • Составление "потоков" событий вместе
  • Операции в стиле Linq
  • Предоставление потоков данных из вашего публичного API

TPL, похоже, хорошо отображается на

  • внутреннее распараллеливание работы
  • выполнение неблокирующей асинхронной работы, такой как веб-запросы
  • выполнение рабочих потоков и продолжений

Одна вещь, которую я заметил с IObservable (Rx), это то, что он становится повсеместным. Попав в базу кода, так как она, несомненно, будет открыта для других интерфейсов, она в конечном итоге появится во всем приложении. Я думаю, это поначалу может быть страшно, но большая часть команды сейчас довольно комфортно с Rx и ей нравится объем работы, который нас спасает.

IMHO Rx будет доминирующей библиотекой над TPL, поскольку она уже поддерживается в .NET 3.5, 4.0, Silverlight 3, Silverlight 4 и Javascript. Это означает, что вам действительно нужно выучить один стиль, и он применим ко многим платформам.

EDIT : Я изменил свое мнение о том, что Rx доминирует над TPL. Они решают разные проблемы, поэтому сравнивать их не следует. В .NET 4.5 / C # 5.0 ключевые слова async / await еще больше свяжут нас с TPL (что хорошо). Для глубокого обсуждения Rx против событий против TPL и т. Д., Прочитайте первую главу моей онлайн-книги IntroToRx.com

7 голосов
/ 13 апреля 2015

Я бы сказал, что TPL Dataflow охватывает специализированное подмножество функций в Rx. Поток данных предназначен для обработки данных, которая может занимать измеримое количество времени, тогда как Rx - для событий, таких как положение мыши, состояния ошибок и т. Д., Где время обработки незначительно.

Пример: ваш обработчик «подписки» асинхронный, и вы хотите, чтобы в каждый момент времени было не более одного исполнителя. С Rx вы должны блокировать, другого пути нет, потому что Rx асинхронно-независимый и во многих местах не угрожает асинхронности особым образом.

.Subscribe(myAsyncHandler().Result)

Если вы не блокируете, то Rx будет считать, что действие завершено, пока обработчик все еще выполняется асинхронно.

Вы можете подумать, что если вы делаете

.ObserveOn(Scheduler.EventLoopSchedule)

чем проблема решена. Но это нарушит ваш рабочий процесс .Complete (), потому что Rx будет думать, что это сделано, как только запланирует выполнение, и вы выйдете из приложения, не ожидая завершения асинхронной операции.

Если вы хотите разрешить не более 4 одновременных асинхронных задач, чем Rx не предлагает ничего из коробки. Возможно, вы можете что-то взломать, внедрив собственный планировщик, буфер и т. Д.

TPL Dataflow предлагает очень хорошее решение в ActionBlock. Он может ограничивать одновременные действия до определенного числа, и он понимает асинхронные операции, поэтому вызов Complete () и ожидание Completed сделают именно то, что вы ожидаете: ожидание завершения всех выполняющихся асинхронных задач.

Еще одна особенность TPL - это «противодавление». Допустим, вы обнаружили ошибку в своей процедуре обработки и вам необходимо пересчитать данные за последний месяц. Если вы подписываетесь на свой источник, используя Rx, и ваш конвейер содержит неограниченные буферы или ObserveOn, то вам не хватит памяти в считанные секунды, потому что источник будет продолжать читать быстрее, чем может обработать обработка. Даже если вы реализуете блокировку потребителя, ваш источник может страдать от блокировки вызовов, например, если источник асинхронный. В TPL вы можете реализовать источник как

while(...)
    await actionBlock.SendAsync(msg)

, который еще не блокирует источник, будет ожидать, пока обработчик перегружен.

В целом я обнаружил, что Rx хорошо подходит для действий, которые являются временными и вычислительными. Если время обработки становится значительным, вы попадаете в мир странных побочных эффектов и эзотерической отладки.

Хорошая новость заключается в том, что блоки потока данных TPL прекрасно работают с Rx. Они имеют адаптеры AsObserver / AsObservable, и вы можете при необходимости прикрепить их к середине конвейера Rx. Но у Rx гораздо больше шаблонов и вариантов использования. Поэтому мое практическое правило - начинать с Rx и добавлять поток данных TPL по мере необходимости.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...