简体   繁体   中英

Does IEnumerable<T>.Count() actually work for IObservable<T>?

Is there an example out there showing me how the Observable.Count<TSource> Method actually works? The examples I come up with appear to return a count wrapped in an observable instead of the expected count.

For example, I expect 1 to be returned from this:

System.Diagnostics.Debug.WriteLine((Observable.Return<string>("Hello world!")).Count());

Will 1 be returned in the future (because, after all, it is an asynchronous sequence)? Or am I missing a few things fundamental? As of this writing, I actually assume that .Count() will return the results of T and grow over time as long a results are pushed out. Really? Yes.

The aggregate operators in Rx work a bit differently than in LINQ - they do not immediately return a value, they return a future result (ie we can only know what the final Count is once the Observable completes).

So if you write:

Observable.Return("foo").Count().Subscribe(x => Console.WriteLine(x));
>>> 1

because, after all, it is an asynchronous sequence

This actually isn't exactly true. Here, everything will be run immediately, as soon as somebody calls Subscribe . There is nothing asynchronous about this code above, there are no extra threads, everything happens on the Subscribe.

I think that using an observable that returns immediately and also using the async/await syntax as rasx did in the comments is confusing matters rather too much.

Let's create a stream with 5 elements that come back one every second and then complete:

private IObservable<long> StreamWith5Elements()
{
    return Observable.Interval(TimeSpan.FromSeconds(1))
                     .Take(5);
}

We can call it using async/await magic as in this LINQPad friendly example:

void Main()
{
    CountExampleAsync().Wait();
}

private async Task CountExampleAsync()
{
    int result = await StreamWith5Elements().Count();
    Console.WriteLine(result);
}

But it's misleading what's going on here - Count() returns an IObservable<int> , but Rx is super-friendly with await and converts that result stream into a Task<int> - and the await then hands back that task's int result.

When you use await against an IObservable<T> , you are implicitly saying that you expect that observable to call OnNext() with a single result and then call OnComplete() . What actually happens is that you will get a Task<T> that returns the last value sent before the stream terminated. (Similar to how AsyncSubject<T> behaves).

This is useful because it means any stream can be mapped to a Task , but it does require some careful thought.

Now, the above example is equivalent to the following more traditional Rx:

void Main()
{
    PlainRxCountExample();
}

private void PlainRxCountExample()
{
    IObservable<int> countResult = StreamWith5Elements().Count();
    countResult.Subscribe(count => Console.WriteLine(count));

    /* block until completed for the sake of the example */
    countResult.Wait();
}

Here you can see that Count() is indeed returning a stream of int - to provide an asynchronous count. It will return only when the source stream completes.

In the early days of Rx, Count() was in fact synchronous.

However, that's not a terribly useful state of affairs since it "Exits the Monad" - ie brings you out of IObservable<T> and prevents you from further composition with Rx operators.

Once you start "thinking in streams", the asynchronous nature of Count() is quite intuitive really, since of course you can only provide a count of a stream when it's finished - and why hang around for that?? :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM