Asked by:

Asynchronous code in OnNext will break the Rx contract

Question

I can't find an asynchronous way to keep a subscription synchronized while the subscription executes asynchronous code.

Essentially, I want to use and combine Rx with a lot of (standard) async/await code and I really hope there is a first-class way to do it. Examples with Console.WriteLine and Thread.Sleep are nice, but now how does Rx really fit in with .NET 4.5???

Because sure I can use Task.WaitAll, but that defeats the asynchronicity of my code and will in the end lead to the old problem of having too many threads blocking for !another! thread doing the work that the blocked thread should have been doing.

Here's some code to show about what I'm trying to do. It yields 2 observations, 1 seconds apart and runs processing on each that takes 2 seconds, writing to the console when items are observed and when starting/stopping processing.

Here we take the processing out of the Subscription and make it part of the sequence. This allows us to stay within the asynchronous sequence paradigm, or staying within the 'Mondad'.

We do this because we can think of the processing of the value as an Async process. This Async processes can be thought of either as a Task or a Single value Observable Sequence. As Rx excels at composing observable
sequences we take advantage of that. We also take advantage of the Rx contact that ensures that sequences are guaranteed to be delivered in order.

I also agree with Lee that the reason you're getting the first output instead of the second is because you're subscribing asynchronously. That's a fairly common
code smell. It seems that all you're really asking about is why the
observer effect isn't happening in your example. In order to see the output slow down due to observations, you must subscribe synchronously.

The following example illustrates how the observer effect can slow down subsequent notifications. Note the call to
Wait on Task.Delay, which blocks the observable from pushing the next value for an extended period of time.

Well yes but sure the code that I encountered this in was a rewrite (from TPL await/async) to IObservable and this code did not simply perform Task.Delay as processing...

In fact that Task was reading a NetworkStream which yielded deserialized messages, but upon using IObservable turned out to read data out of order ...that sucked, believe me)

If the takeaway is that I need to compose everything asynchronous into the IObservable query then I guess I get it, but whether I'll like it...I doubt that, but I'll give it another go.

Yet it means - from my point of view - that most of the processing will amount to producing another IObservable which no one will consume. That's weird, isn't it. Now the paradigm isn't 'all the way async' but 'all the way Rx' which is a bad thing in my
point of view. You see this 'bug' surfacing just because I declare an async anonymous delegate...which is such a powerful option in .NET 4.5 without Rx...

I mean if I do regular UI processing of mouse events, for the overall usability of event streams (think outside the Rx box), whatever must happen in response to those events 1) has to be able to be regular code as opposed to another observable stream and
2) has to be able to support async/await (or it would not be 'regular code' in .NET 4.5)

I just learned about Monets - or whatever - and it's all nice and so forth, but must I now conclude that Rx code again imposes rewrites to get code working the way it needs to???

Now you argue that my code is wrong. Sure, I know. I had already produced your output using Thread.Sleep ...

But

The thing that I'm surprised about is that I wanted an asynchronous subscription to be a first-class feature, not a code smell. See my response above.

There's enough examples that make subscribers process data in parallel if I wanted that, but now I understand there's no way of enabling regular asynchronous code at the 'consumer' end of an observable sequence. That's just a plain pity.

Next to my other response, can you explain to me the difference between your last output and the output below. Yours produces 'emitting' messages 1 second apart while the example below delays them until after the processing and I can't see why..is it the
SelectMany or the Task.ToObservable?

It's better I think if that NetworkStream reads messages as fast as possible and each producer (I intend to have many) will see them as fast as they are individually.

> If the takeaway is that I need to compose everything asynchronous into the IObservable query [snip]

Often it's simply a matter of cardinality.

IObservable<T> represents an asynchronous computation that generates a sequence of values.Task<T> represents an asynchronous computation that generates a single value.

Therefore, IObservable<T> can also represent an asynchronous computation that generates a single value only, thus it provides a superset of
Task<T>.

Choose the model that is appropriate for your requirements.

> Yet it means - from my point of view - that most of the processing will amount to producing another IObservable which no one will consume.

How do you figure? That's actually another code smell in Rx. Sometimes it's useful to subscribe to an observable for its side-effects only, but not typically.

> Now the paradigm isn't 'all the way async' but 'all the way Rx' which is a bad thing in my point of view.

Rx provides an awaitable implementation for IObservable<T> and it provides operators that convert to/from IObservable<T> and
Task<T>.

Furthermore, calling Subscribe is generally asynchronous. Whether it is or not is the responsibility of the observable, not the observer.

For example, Observable.Interval uses pooled threads by default, but you can pass in any
IScheduler.

> 1) has to be able to be regular code as opposed to another observable stream

Do you mean in terms of cardinality? Clearly if your algorithm potentially needs to generate more than a single value, then
IObservable<T> is a better model; otherwise, you may find that
Task<T> is more suitable, though not entirely necessary. In that case, as Lee has shown, you can easily convert a task via
ToObservable so that it may be composed with your query.

> 2) has to be able to support async/await (or it would not be 'regular code' in .NET 4.5)

Rx offers the ability to write
async iterators via an overload of Observable.Create.

Well not wrong, per se. My argument was simply that the §4.2 contract applies to synchronous observations only. My intention was to show how blocking produces the expected behavior. Running code asynchronously in a subscription
is a code smell, but it's not necessarily wrong. However, it certainly breaks the §4.2 contract.

> The thing that I'm surprised about is that I wanted an asynchronous subscription to be a first-class feature, not a code smell.

It's a code smell because when you introduce your own asynchrony in the call to
Subscribe, you're no longer within the query. Rx has nothing to do with that. Rx is all about controlling complexity in asynchronous computations through composition.
Subscribe exits the composition.

The most powerful and common operator in Rx is SelectMany. It represents sequential asynchronous computation. If you want to compose together an observable and a task so that they execute sequentially, then you must convert the
task into an observable and apply SelectMany. (Edit: Rx 2.0 enables composition of tasks to observables without conversion!)

Note that SelectMany executes the task for each value in the observable, thus if the task introduces concurrency, then you may have tasks running simultaneously. There are various ways of controlling this if
it's not what you want. For example, you can use Concat instead of
SelectMany to avoid concurrency altogether (assuming that you're composing a
cold observable, though Task<T> is hot so you may have to wrap it with
Observable.Defer).

Another approach uses Switch to ensure that only the latest observable is active, as shown in my previous reply.

> now I understand there's no way of enabling regular asynchronous code at the 'consumer' end of an observable sequence.

Sure there is. You can add an asynchronous observer as you have, just don't expect Rx to know about it and maintain any of its contracts. If you want Rx to know about it, then you must compose it into the query; e.g., via
SelectMany.