Search code examples
c#genericssystem.reactivereactive-programmingtpl-dataflow

Search implementation using Rx.Net


I am creating a search page implementation using Rx in C#. I have created a generic search method to search a keyword and add list of result on UI. Below is the code:

Generic Search Method:

   public static IObservable<TResult> GetSearchObservable<TInput, TResult>(INotifyPropertyChanged propertyChanged,
                string propertyName, TInput propertyValue, Func<TInput, TResult> searchFunc)
            {
                // Text change event stream
                var searchTextChanged = Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(
                                                  ev => propertyChanged.PropertyChanged += ev,
                                                  ev => propertyChanged.PropertyChanged -= ev
                                                  )
                                                  .Where(ev => ev.EventArgs.PropertyName == propertyName);

                // Transform the event stream into a stream of strings (the input values)
                var inputStringStream = searchTextChanged.Throttle(TimeSpan.FromMilliseconds(500))
                                                         .Select(arg => propertyValue);

                // Setup an Observer for the search operation
                var search = Observable.ToAsync<TInput, TResult>(searchFunc);

                // Chain the input event stream and the search stream, cancelling searches when input is received
                var results = from searchTerm in inputStringStream
                              from result in search(propertyValue).TakeUntil(inputStringStream)
                              select result;

                return results;
            }

Usage of Search Generic Method:

  var rand = new Random();
            SearchObservable.GetSearchObservable<string, string>(this, "SearchString", SearchString, search =>
            {
                Task.WaitAll(Task.Delay(500));

                return SearchString;
            })
            .ObserveOnDispatcher()
            .Subscribe(rese => 
            {
                LstItems.Clear();

                LstItems.Add(SearchString);

                // Heavy operation lots of item to add to UI
                for(int i = 0; i < 200000; i++)
                {
                    LstItems.Add(rand.Next(100, 100000).ToString());
                }

                Result = rese;
            });

There are 2 problems in this code need help to fix those:

  1. The search keyword that is passed in the generic method line from result in search(propertyValue).TakeUntil(inputStringStream) the value is always null as propertyValue is string type hence passed as value into the method. How to send the updated value in search method?

  2. When a heavy UI operation is been done on Subscribe method as in the example of adding some large number of random numbers. This cause issue when search is performed repeatedly. Ultimately the UI blocks. How can i fix this?

For 2nd point is there any way to cancel the previous UI operation and run a new one. This is just a thought. But need some suggestions to fix these issue.


Solution

    1. You can pass a function that reads property value i.e. instead of TInput propertyValue your method should accept Func<TInput> propertyValueGetter.

    You can also use ReactiveUI library. In this case your code would look something like this:

    this.WhenAnyValue(vm => vm.SearchString)
        .Throttle(TimeSpan.FromMilliseconds(500))
        .InvokeCommand(DoSearchString);
    
    DoSearchString = ReactiveCommand.CreateAsyncTask(_ => {
       return searchService.Search(SearchString);
    });
    
    DoSearchString.ObserveOn(RxApp.MainThreadScheduler)
                  .Subscribe(result => {
                      LstItems.Add(result);
                  })
    
    1. I don't believe there is a general answer for this question. Just do as much as possible on TaskPool and when you data is ready to be displayed switch to UI thread with ObserveOn. In your case you could perhaps insert items by batches, not all at once.