What is the best way to call asynchronous methods using reactive power + throttle
I am trying to solve my first problem using Rx + ReactiveUI and I am looking for best practices to solve the problem by showing an input box that will display suggestions as soon as the user starts typing.
As per the code example below, what's the best way to load sentences asynchronously? Using Subscribe
or using Select Many
? Or is there a better way to do it beneth these two?
this.SearchTerms = this.ObservableForProperty(x => x.SearchTerm)
.Throttle(SuggestionThrottle, RxApp.MainThreadScheduler)
.Value()
.SelectMany(async s => await this.LoadSearchSuggestions(s)); // 1st Possibility
this.SearchTerms.Subscribe(this.LoadSearchSuggestions); // 2nd Possibility
source to share
You have to call Subscribe
anyway.
Queries in Rx use lazy evaluation, which means that simply defining the query does not trigger it. Lazy evaluation allows you to build a query by conditionally, define the query only once and store it in a field for later, or pass a link without any side effects until you call Subscribe
.
Without a call, Subscribe
your request will remain inactive.
Subscribe
activates the request by navigating to the observable tag IObserver<T>
, or you can use its overloads, which allow you to individually allocate handlers OnNext
, OnError
and / or OnCompleted
that Rx converts to IObserver<T>
for you. This is the IObserver<T>
one that receives request notifications.
There is a parameterless overload Subscribe
that internally uses a silent observer with the intention of starting a query just for its side effects. For example, in your case, if you used SelectMany
to do all the work of loading sentences and you didn't need a separate one IObserver<T>
, then you should run the query by invoking a parameterless overload Subscribe
.
In most cases, you shouldn't use parameterless overloading Subscribe
. The point Subscribe
is that IObserver<T>
(or the individual handlers) you pass to it are designed to cause side effects of your request to occur. By only causing side effects in Subscribe
or, for example, in a statement Do
, the query is much easier to reason about and maintain.
However, there is one fairly common scenario where using parameterless overloading Subscribe
makes sense: if the side effects of your request are caused by an asynchronous method, then using SelectMany
along with unnoticed overloading is Subscribe
better.
The reason is simple: it SelectMany
is a sequential composition operator that allows you to call an asynchronous method as a sequential step within your request. Therefore, SelectMany
links unsubscribing to canceling your asynchronous computation. Eliminating a subscription (presented IDisposble
that is returned from a call to Subscribe
) causes the one CancellationToken
provided by special asynchronous operator overloads SelectMany
to be signaled to be canceled. Your asynchronous method can control CancellationToken
to complete its evaluation earlier.
There are no overloads Subscribe
that an asynchronous observer takes and OnNext
returns as a result Task
. But even if you had to call a void-returning async method in your handler OnNext
, your async method won't be signaled when the subscription is posted.
Note that both of your code examples are slightly flawed. In the first example, there is no need for the keywords async
and await
. As stated above, there are special overloads SelectMany
that accept the Task<T>
-opening selector function and additional overloads that provide CancellationToken
this function. You should probably use the latter.
The second example shouldn't compile, assuming what it LoadSearchSuggestions
returns Task
. (If the ReactiveUI library, or some other library you link to, provides an overload Subscribe
that accepts a Task
-returning function , in which case you will have to consult its documentation.)
Disallowing the latter and assuming the rest of your request is correct, here's what you should do:
this.SearchTerms = this.ObservableForProperty(x => x.SearchTerm)
.Throttle(SuggestionThrottle, RxApp.MainThreadScheduler)
.Value()
.SelectMany(LoadSearchSuggestionsAsync)
.Subscribe();
where is LoadSearchSuggestionsAsync
defined as follows:
async Task<Unit> LoadSearchSuggestionsAsync(string term, CancellationToken cancel)
{
...
return Unit.Default;
}
Note that Unit represents void in Rx. This is necessary because an asynchronous method that does not return a generic method Task
cannot be used with SelectMany
. If you have actual data to return instead, just replace Unit
with your data type. Then you can also pass a handler OnNext
to Subscribe
and do something with the return value, like logging.
source to share