3

I've recently gotten into Rx and I'm using it to help me pull data from several APIs in a data mining application.

I have an interface that I implement for each API, which encapsulates common calls to each API, e.g.

public interface IMyApi {

    IObservable<string> GetApiName(); //Cold feed for getting the API's name.

    IObservable<int> GetNumberFeed(); //Hot feed of numbers from the API

}

My question is around cold IObservables vs Tasks. In my mind, a cold observable is basically a task, they operate in much the same way. It strikes me as strange to be 'abstracting' a Task away as a cold observable, when you could argue that a Task is all you need. Also using a cold observable to wrap Tasks hides the nature of the activity, since the signature looks the same as a hot observable.

Another way I could represent the above interface is:

public interface IMyApi {

    Task<string> GetApiNameAsync(); //Async method for getting the API's name.

    IObservable<int> GetNumberFeed(); //Hot feed of numbers from the API

}

Is there some conventional wisdom on why I shouldn't mix and match between Tasks and IObservables?

Edit: To clarify - I've read the other discussions posted and understand the relationship between Rx and TPL, but my concerns are mainly about whether or not it's safe to combine the two in an application and whether it can lead to bad practice or threading and scheduling pitfalls?

Sebastian Nemeth
  • 5,505
  • 4
  • 26
  • 36

3 Answers3

6

My question is around cold IObservables vs Tasks. In my mind, a cold observable is basically a task, they operate in much the same way

It is important to note that this is not the case, they are very different. Here's the core difference:

// Nothing happens here at all! Just like calling Enumerable.Range(0, 100000000)
// doesn't actually create a huge array, until I use foreach.
var myColdObservable = MakeANetworkRequestObservable();

// Two network requests made!
myColdObservable.Subscribe(x => /*...*/);
myColdObservable.Subscribe(x => /*...*/);

// Only ***one*** network request made, subscribers share the
// result
var myTaskObservable = MakeATask().ToObservable();
myTaskObservable.Subscribe(x => /*...*/);
myTaskObservable.Subscribe(x => /*...*/);

Why is this important? Several methods in Rx such as Retry depend on this behavior:

// Retries three times, then gives up
myColdObservable.Retry(3).Subscribe(x => /*...*/);

// Actually *never* retries, and is effectively the same as if the
// Retry were never there, since all three tries will get the same
// result!
myTaskObservable.Retry(3).Subscribe(x => /*...*/);

So in general, making your Observables as cold will generally make your life easier.

How can I make a Task Cold?

Use the Defer operator:

var obs = Observable.Defer(() => CreateATask().ToObservable());

// CreateATask called *twice* here
obs.Subscribe(/*...*/);
obs.Subscribe(/*...*/);
Ana Betts
  • 73,868
  • 16
  • 141
  • 209
4

The difference between Task and IObservable is not hot vs. cold: Task-returning methods can pretty much be "cold" (return new Task on every call) or "hot" (always return the same Task), just like IObservables.

The difference between the two is that IObservable represents a sequence of results, while Task represents a single result.

So, in cases when you'll always have a single result (or an error), use Task, when you can have any number of results, use IObservable.

svick
  • 236,525
  • 50
  • 385
  • 514
  • Actually, this is a really good insight... I hadn't thought that a Task could be considered 'hot'. – Sebastian Nemeth Nov 16 '14 at 13:46
  • This is true, but realistically nobody uses a cold `Task` (i.e., the `Task` constructor). It's pretty safe to say that `Task` is *hot* in general, especially in the world of `async/await`. It's the function that makes a `Task` cold; as soon as you've got a reference to a `Task`, you can pretty much bet that it's `hot`. Not true for `IObservable` in general. – Dave Sexton Nov 16 '14 at 13:51
  • @DaveSexton Yeah, there are differences in how you can implement coldness using `Task` and `IObservable`. But I think those differences are not that important. – svick Nov 16 '14 at 13:55
  • 1
    `Task` is still *hot*; by the time you get its reference it may have already completed. It's the deferring function that's making it *cold*. Wrapping anything in a function makes it *cold*, so you could do the same with a `hot` observable; e.g., `Observable.Defer`. So a *cold* `Task` is one that is constructed by the `Task` constructor, and you must start it manually for it to have any side effects, like calling `Subscribe` on an `IObservable`. http://davesexton.com/blog/post/Hot-and-Cold-Observables.aspx – Dave Sexton Nov 16 '14 at 14:01
  • My previous comment was in reference to the previous comment by @svick that was deleted. :) – Dave Sexton Nov 16 '14 at 14:02
4

There's no problem mixing the models, and in fact even the Rx team has included many adaptive operators in Rx. For example, ToTask, ToObservable, SelectMany, DeferAsync, StartAsync, ToAsync, etc. You can even await an IObservable<T> within an async method.

The primary difference that should affect your decision is cardinality:

IObservable<T> is [0,∞]

Task<T> is [0,1]

So if you need to represent only a single return value, then strongly consider using Task<T>.

Dave Sexton
  • 2,562
  • 1
  • 17
  • 26