我读到说,使用IObservable时,我们有异步调用,没有阻塞,一切都是推送式的。
但是,但是,但是...
这到底意味着什么?什么是Push和Pull式的结构?
因为在我的看法中,在IEnumerable中我们也可以将数据推送到结构中并从中拉取数据,我真的在那些技术术语和概念中迷失了方向。
请用普通人易懂的方式向我解释这两种结构之间的区别以及推送式和拉取式结构之间的区别。
谢谢。
请用通俗易懂的方式解释一下它们之间的区别。
好的,让我们来烤面包吧。这是我最正常、最人性化的行为。
推式:
// I want some toast. I will pull it out of the toaster when it is done.
// I will do nothing until the toast is available.
Toast t = toaster.MakeToast();
t.AddJam();
// Yum.
基于推送:
// I want some toast. But it might take a while and I can do more work
// while I am waiting:
Task<Toast> task = toaster.MakeToastAsync();
Toast t = await task;
// await returns to the caller if the toast is not ready, and assigns
// a callback. When the toast is ready, the callback causes this method
// to start again from this point:
t.AddJam();
// Yum.
当你想要获取结果时,你调用一个函数并等待函数返回。
当你想要接收结果时,你调用一个异步函数并等待结果。当结果可用时,它会被推送到回调函数中,然后该方法恢复执行。
IEnumerable<T>
只是一系列pulls的序列;每次想要拉取结果时,你都需要调用MoveNext
并获取T
。而IObservable<T>
则是一系列pushes的序列;你注册一个回调函数,每当有新的T
可用时就会调用该回调函数。
换句话说:IEnumerable<T>
在逻辑上是一系列Func<T>
的调用。而IObservable<T>
在逻辑上是一系列Task<T>
的延续。不要让它们是“序列”这个事实误导你;这只是附带的。其基本思想是函数是同步的;你调用它们并同步获取结果;如果需要等待,就等待。任务是异步的;你启动它们并在结果可用时异步获取结果。
在IObservable
和await
出现之前,这个思想已经存在于C#中。另一种看待它的方式是:pull-based就像函数调用,push-based就像事件处理程序。普通的函数调用是在需要时调用的。而事件处理程序则是在发生事件时调用你。事件是C#语言本身表示观察者模式的方式。但是,事件始终逻辑上形成一个序列,因此我们可以像操作拉取项的序列一样操作推送项的序列。因此,IObservable
被发明了出来。
命名混乱,我认为它们并没有很好地表达它们打算传达的概念。例如,在 Java 世界中,IEnumerable 是 Iterable,更接近于 .NET 中的意图。
我认为最简单的方法是将 IEnumerable 和围绕它们的 LINQ 结构想象成 "从某个源获取一些数据,并按照这种方式进行过滤/分组..." 而 Observables 则可以被认为是可以反应的输入流。我认为将 observables 视为连续体并不一定有帮助。
IObservable<T>
与一系列Task<T>
延续进行比较。使用可观察对象,您只需订阅一次即可获得多个通知。而对于任务序列,您必须单独订阅每个任务(通过等待它),以从每个订阅中获取单个通知。我还想知道您对IAsyncEnumerable
的看法。我很困惑这些应该被视为拉或推结构。似乎两种观点都有论据。 - Theodor Zoulias