为什么Task<T>没有实现IObservable<T>?

9

Task<T>.ContinueWithIObservable<T>.Subscribe 有着相同的基本概念。它们非常相似,以至于 Reactive Extension 提供了一个转换扩展方法。由于 IObservable<T> 是 BCL 的一部分,因此没有理由不实现 IObservable<T>。那么,为什么 Task<T> 没有实现 IObservable<T> 呢?


2
一个恰当的回答需要TPL团队的成员或者对该团队决策过程有类似深入了解的人来回答,除非我只是错过了一些外部人士显而易见的东西。 - Simon Whitehead
通常来说,“为什么”问题不是好问题。 - Cole Tobin
3
“为什么”问题是非常好的问题!不过,必须承认,它们可能不适合在StackOverflow上提问。 ;) - James Manning
@SimonWhitehead,我怀疑“TPL团队”被称为“PFX团队”?而且它的成员Stephen Toub 已经回答了这个问题。 - Gennady Vanin Геннадий Ванин
4个回答

3

这篇文章由Rx团队在他们的2.0测试版博客中讲述得非常好。虽然这是一篇非常长的文章,但里面有很多很棒的信息。

简略总结= IObservable用于未来结果的序列,Task用于单个未来结果

我认为最好解释了它们之间关系的图表在该帖子中,但可惜它在文章中太深了(而且我提到过,那篇文章非常大 :)

Rx vs Task

虽然整篇文章都值得阅读(依我之见),但与Rx / Task / async/await如何配合开始的相关文本标题为“Rx v2.0和.NET 4.5 “async” / “await” - 更好的协作故事”


Parrallel Extensions团队在一篇博客文章中表示,Task<T>非常适合IObservable<T>。Rx团队不负责实现他们的接口的人。 - user1377345
Stephen在pfxteam上发布的帖子比Rx 2.0帖子早两年,显然也早于Rx 2.0。Task可以与Rx 2.0中描述的IObservable一起使用,但正如Stephen在2010年的那篇文章中所示,它是一个单结果可观察对象。IObservable是关于未来值集合的。总是将任务视为IObservable是可能的,但即使只传递单个值,如果强制同步代码通过IEnumerable传递所有内容,您最终会得到相同类型的代码。 :) 关键点是概念上的差异,这个图解释得非常好,我认为。 - James Manning
我可以看出在 IEnumerable<T> 的情况下代码有什么问题;然而,我不明白为什么 Task<T> 实现 IObservable<T> 是错误的,因为它应该在逻辑上实现它,因为它包含相同的方法,并且不会导致像将每个单独的值视为可枚举一样的可怕代码。 - user1377345
为什么这不会导致糟糕的代码?每个正在获取Task<T>并进行简单等待(或其他操作)的地方现在都需要更改以处理未来结果的持续流。如果您的回应是将其记录为仅返回单个未来结果,那么与仅返回1个项的IEnumerable或T[]相同。自然的反应是“如果您只返回1个项,那么只需返回该项!”(对于同步使用T,对于异步使用Task<T>)。 - James Manning
“包含相同方法”我不明白 - IObservable只有一个方法Subscribe,在IEnumerable或Task中没有这样的方法,据我所知?http://msdn.microsoft.com/en-us/library/dd990377.aspx - James Manning

2
我只能猜测,但我认为有一些很好的理由:
  1. Task<T>IObservable<T>实际上并没有相同的基本概念。是的,两者都是异步的,但这几乎是它们之间所有共同点的终点。两者之间的主要区别在于,Task<T>始终具有单个值,而IObservable<T>可以拥有尽可能多的值(包括零或少量)。

    如果Task<T>实现了IObservable<T>,那么这将类似于.Net中每个实现IEnumerable<T>,返回单个结果,即该对象本身。

  2. Task<T>转换为IObservable<T>是微不足道的。可以使用Rx中的ToObservable()扩展方法,也可以通过自己编写来完成。这使得你所要求的变得不太必要。


并行扩展团队在一篇博客文章中表示,Task<T>非常适合IObservable<T>。第一点的第二部分是不正确的。从逻辑角度来看,任务是可观察和反应性的;而单个对象不是集合,除非它实现了接口。 - user1377345
1
@SaagarAhluwalia 是的,Task 是可观测的,但它是单个值的可观测对象。IObservable 表示多个可观测值。我认为这并不不正确,这些就是两种类型的定义。 - svick
正如你所说,可观察对象“可以拥有任意多个值(或者很少,包括零)”。因此,它是实现IObservable的完美候选者,即使它只表示单个值。(实际上,它是零到一个值。) - Patrik Hägne
根据这种逻辑,您应该能够将每个对象视为包含该单个对象的集合。例如,int 应该实现 IEnumerable<int>。我不认为这是一个好主意,所以我认为 Task 也不应该实现 IObservable - svick
不,它根本不遵循相同的语义,你的逻辑完全错了。一个简单的值不应该是 IEnumerable<T>,就像一个简单的值不应该实现 IObservable<T> 一样。任务(Task)和简单值之间有很大的区别,任务代表着可能会失败的操作。因此它是可观察的。这更类似于让 Nullable<T> 实现 IEnumerable<T>,这是有些意义的。 - Patrik Hägne
@svick,让Task<T>实现IObservable<T>会有什么缺点?即使您的推理成立,我也看不出任何缺点,只有好处。 - Patrik Hägne

2
我希望引用微软的Stephen Toub在他的MSDN博客文章中所说的话:

ParallelExtensionsExtras Tour - #2 - Task.ToObservable MSDN博客文章:

事实上,尽管 Task<TResult> 目前尚未实现 IObservable<T> 接口,但它实际上非常适合实现该接口。可观察对象表示由任意数量的值组成,以流的结束或异常为终止。而 Task<TResult> 恰好符合这种描述:它要么完成一个单独的值,要么抛出异常。虽然未来的 .NET Framework 版本可能会看到 Task<TResult> 实现 IObservable<T> 接口,但我们可以通过将其作为 Task<TResult> 的扩展方法来实现相关行为;实际上,RxParallelExtensionsExtras 都包含了这样的扩展方法。

“FT” 的含义不确定,除了 MSFT 是纳斯达克股票代码代表微软公司之外,"MS" in "MSFT" 显然是指微软。

2
MSFT只是Microsoft的缩写:MicroSoFT - Brandon
@Brandon,哇,非常感谢。终于找到了。一直在考虑分开单词(比如“基础”和“团队”)。 - Gennady Vanin Геннадий Ванин

0

原因是,虽然Task<T>类型在.NET框架中,但反应式扩展(Rx)和IObservable<T>不在其中。 System.Reactive程序集具有System.Threading.Tasks作为依赖项,因此后者中没有任何内容可以引用前者。

如果将来他们将Rx添加到.NET中,他们可能会像您建议的那样做。

编辑:

实际上,我可能说得太快了。请查看文档。在.NET 4和4.5中,IObservable<T>接口实际上存在于mscorlib程序集中。因此,我上面说的不是完全有效的原因。我正在修订我的猜测原因为“因为他们尚未将Rx集成到.NET中”,而不是“由于循环依赖关系目前不可能”。


我希望这里有一个“但是”的字眼 - rx 真的很酷。 :) - JerKimball
1
@JerKimball 由于 Rx 是开源的,这种情况永远不会发生。无论如何,这样做更好。 - Richard Anthony Freeman-Hein
1
实际实现接口所需的唯一要素就是接口本身。这就像说在 .Net 3.0 中没有 Linq,因此没有人应该实现 IEnumerable<T> 接口一样。IObservable<T> 是独立存在的,不需要 Rx 库来运行,因为它的主要功能仍然是订阅。 - user1377345

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接