Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reactive Extensions Switch() in search

Classic live search example:

var searchResults = from input in textBoxChanged
                    from results in GetDataAsync(input)
                    select results;

GetDataAsync returns a:

 Task<List<DataRecord>>

Here is obviously a race condition so if the search triggered by the second input returns before the first one, the results from the first input comes after and therefore gives me the wrong data.

I keep reading everywhere that the .Switch() operator will magically solve this, but how?

.Switch() only exists on:

 IObservable<IObservable<T>>
like image 223
Magnus Ahlin Avatar asked Nov 03 '13 12:11

Magnus Ahlin


1 Answers

Assumption

I will assume that textBoxChanged has been created by something like:

var textBoxChanged = Observable.FromEventPattern(x, "TextChanged")
                               .Select(evt => ((TextBox)evt.Sender).Text);

Preventing the race condition when using SelectMany

from... from... in a LINQ comprehension translates to a SelectMany, which is what you are using. Rx is smart enough to translate the Task<List<DataRecord>> returned by GetDataAsync(input) into an IObservable<List<DataRecord>>.

The problem is that you want to prevent results coming back from all but the most recently submitted search request.

To do this, you can leverage TakeUntil. It has the following signature:

public static IObservable<TSource> TakeUntil<TSource, TOther>(
    this IObservable<TSource> source,
    IObservable<TOther> other
)

And it returns the values from the source observable sequence until the other observable sequence produces a value.

We can employ it like this:

var searchResults = from input in textBoxChanged
                    from results in GetDataAsync(input).ToObservable().TakeUntil(textBoxChanged)
                    select results;

This will prevent the race condition, but will also subscribe twice to textBoxChanged.

Using Switch instead

It is such a useful pattern that an alternative approach was introduced using the Switch() operator which also takes care of the double subscription.

Instead of using SelectMany, simply project the input directly into the search query - this will give a return type of an IObservable<IObservable<List<DataRecord>>, a stream of streams. Switch will jump from stream to stream only returning the most recent stream. This is the equivalent of the SelectMany/TakeUntil combo:

var searchResults = (from input in textBoxChanged
                     select GetSearchResults(input).ToObservable())
                    .Switch();

I highly suggest looking at the Rx Hands on Lab that explains this in much more detail.

like image 83
James World Avatar answered Nov 14 '22 18:11

James World