Please in a normal and human way explain to me the difference
OK, let's make some toast. That's the most normal human thing I do.
Pull based:
// 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.
Push based:
// 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.
You want to pull a result, you call a function and you do nothing else until the function returns.
You want a result pushed to you, you call an async function and await the result. When the result is available it is pushed at the callback, which resumes the method where it needs to be.
IEnumerable<T>
is just a sequence of pulls; you call MoveNext
every time you want to pull a result and you get a T
. IObservable<T>
is just a sequence of pushes; you register a callback, and it is called every time a new T
is available.
Put another way: IEnumerable<T>
is logically a sequence of Func<T>
invocations. IObservable<T>
is logically a sequence of Task<T>
continuations. Don't let the fact that they are sequences confuse you; that's incidental. The fundamental idea is that functions are synchronous; you call them and get a result synchronously; if it takes a while, you wait. Tasks are asynchronous; you start them and get the result asynchronously when it is available.
This idea existed in C# before IObservable
and await
. Another way to look at is is: pull-based is like function calls, push-based is like event handlers. A normal function call, you call it when you want something. An event handler, it calls you when something happens. Events are how the C# language itself represents the observer pattern. But events always logically form a sequence, so it makes sense to be able to manipulate a sequence of pushed items the same way we'd manipulate a sequence of pulled items. And hence, IObservable
was invented.