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