何时会选择使用Rx而不是TPL,或者2个框架是否正交?
据我所知,Rx主要用于提供事件的抽象并允许组合,但它也允许提供异步操作的抽象。 使用Createxx重载和Fromxxx重载并通过处理返回的IDisposable取消。
TPL还通过任务和取消能力提供操作抽象。
我的困境是何时使用哪种情况以及在什么情况下?
答案 0 :(得分:44)
Rx的主要目的不是提供事件的抽象。这只是其结果之一。其主要目的是为集合提供可组合的推送模型。
反应框架(Rx)基于IObservable<T>
是IEnumerable<T>
的数学对偶。因此,我们可以通过IEnumerable<T>
将对象“推送”给我们,而不是使用IObservable<T>
从集合中“拉”项目。
当然,当我们真正去寻找可观察的来源时,比如事件和事件。异步操作是很好的候选者。
反应式框架自然需要一个多线程模型才能观察可观察数据的来源并管理查询和订阅。 Rx实际上大量使用TPL来做到这一点。
因此,如果您使用Rx,则隐式使用TPL。
如果您希望直接控制您的任务,可以直接使用TPL。
但是如果您有想要观察的数据来源并执行查询,那么我会彻底推荐反应框架。
答案 1 :(得分:24)
我喜欢遵循的一些指导原则:
答案 2 :(得分:12)
我喜欢Scott W的要点。举几个具体的例子 Rx很好地映射到
TPL似乎很好地映射到
我注意到IObservable(Rx)的一个原因是它变得无处不在。一旦进入你的代码库,因为它无疑将通过其他接口公开,它最终将出现在你的应用程序中。我想这一开始可能会让人感到害怕,但是大部分团队现在对Rx非常满意并且喜欢它为我们节省的工作量。
IMHO Rx将成为TPL的主流库,因为它已经在.NET 3.5,4.0,Silverlight 3,Silverlight 4和Javascript中得到支持。这意味着您必须学习一种风格,它适用于许多平台。
编辑:我已经改变了主意Rx在TPL上占主导地位。他们解决了不同的问题,所以不应该像这样进行比较。使用.NET 4.5 / C#5.0,async / await关键字将进一步将我们与TPL联系起来(这很好)。有关Rx与事件与TPL等的深入讨论,请查看我的在线图书first chapter的IntroToRx.com
答案 3 :(得分:10)
更新,2016年12月:如果您有30分钟,我建议您阅读Joe Duffy的第一手帐户,而不是我的推测。我认为我的分析很好,但是如果你发现了这个问题,我强烈建议你看看博客文章而不是这些答案,因为除了TPL和Rx.NET之外,他还包括MS研究项目(Midori,Cosmos)。 / p>
<强> http://joeduffyblog.com/2016/11/30/15-years-of-concurrency/ 强>
我认为MS在.NET 2.0问世之后犯了一个大错误。他们从公司的不同部门同时引入了许多不同的并发管理API。
Future<T>
开头并变成Task<T>
)与此同时,许多托管API团队试图使用APM和Threadpool.QueueUserWorkItem()
,而不知道Toub是否会赢得他在mscorlib.dll中发送Future<T>
/ Task<T>
的斗争。最后它看起来像是对冲的,并且在mscorlib中同时发送了Task<T>
和IObservable<T>
,但是在mscorlib中不允许任何其他Rx API(甚至不是ISubject<T>
)。我认为这种对冲最终导致了大量的重复(后来更多),并在公司内外浪费了精力。
有关重复,请参阅:Task
与IObservable<Unit>
,Task<T>
与AsyncSubject<T>
,Task.Run()
与Observable.Start()
。这只是冰山一角。但在更高的层面上考虑:
IEnumerable
样式扩展方法混合“热”流,这意味着您可以非常轻松地永久阻止(在热流上调用First()
永远不会返回)。调度限制(限制并行性)是通过相当奇怪的SubscribeOn()
扩展方法完成的,这些方法非常隐含且难以正确。如果开始学习Rx预留很长时间来学习所有要避免的陷阱。但是,如果编写复杂的事件流或者需要复杂的过滤/查询,Rx实际上是唯一的选择。我不认为Rx有广泛采用的战斗机会,直到MS在mscorlib中发布ISubject<T>
。这很难过,因为Rx包含一些非常有用的具体(通用)类型,如TimeInterval<T>
和Timestamped<T>
,我认为它应该在Core / mscorlib中,如Nullable<T>
。另外,System.Reactive.EventPattern<TEventArgs>
。
答案 4 :(得分:7)
我想说TPL Dataflow涵盖了Rx中的专用功能子集。数据流用于数据处理,可能需要花费大量时间,而Rx用于事件,例如鼠标位置,错误状态等,其中处理时间可以忽略不计。
示例:您的&#34;订阅&#34; handler是异步的,你当时想要的执行程序不超过1个。使用Rx,你必须阻止它,没有别的方法,因为Rx是异步不可知的,并且不会在许多地方以特殊方式威胁异步。
.Subscribe(myAsyncHandler().Result)
如果你没有阻止,那么当处理程序仍在异步执行时,Rx会认为该操作已完成。
如果你这样做,你可能会认为
.ObserveOn(Scheduler.EventLoopSchedule)
问题解决了。但是这会打破你的.Complete()工作流程,因为Rx会认为它一旦安排执行就会完成,你将退出应用程序而不等待异步操作完成。
如果您希望允许不超过4个并发异步任务,则Rx不提供任何开箱即用的功能。也许你可以通过实现自己的调度程序,缓冲区等来破解某些东西。
TPL Dataflow在ActionBlock中提供了非常好的解决方案。它可以将同时操作限制为特定数量,并且它确实理解异步操作,因此调用Complete()并等待Completed将完全按照您的预期执行:等待所有正在进行的异步任务完成。
TPL的另一个特点是&#34;背压&#34;。假设您发现处理程序中存在错误,需要重新计算上个月的数据。如果您使用Rx订阅源代码,并且您的管道包含无界缓冲区或ObserveOn,那么您将在几秒钟内耗尽内存,因为源将保持读取速度超过处理可处理的速度。即使您实现了阻塞使用者,您的源也可能会遇到阻塞调用,例如,如果源是异步的。在TPL中,您可以将源实现为
while(...)
await actionBlock.SendAsync(msg)
在处理程序重载时,不会阻止源。
总的来说,我发现Rx非常适合时间和计算都很轻松的动作。如果处理时间变得充实,那么您就会处于奇怪的副作用和深奥调试的世界。
好消息是TPL Dataflow块对Rx起到了很好的作用。它们具有AsObserver / AsObservable适配器,您可以在需要时将它们粘贴在Rx管道的中间。但是Rx有更多的模式和用例。所以我的经验法则是从Rx开始并根据需要添加TPL数据流。