This post is inspired by a recent post by Mike Taulty. I think you’ll agree it’s a great introductory post on how to use Rx.
I’m a big fan of Rx – it’s an awesome technology. But sometimes I am guilty of over-using it – the old adage that given a shiny Rx hammer every problem looks like a nail definitely applies!
If you haven’t already, please take a look at Mike’s post here to compare with what follows.
While the post was clearly intended as an instructive and motivational example, it did remind me of my own tendency to reach for Rx in similar production-code situations. I often need to remind myself that a non-Rx version can sometimes be more straight-forward. I think this applies here if you look at code re-written to leverage the async/await syntax of C# 5:
Firstly, here is the non-Rx version of MakeObservableMovieDbRequest:
static async Task<T> MakeMovieRequestAsync<T>(
string url,
params KeyValuePair<string, string>[] additionalParameters)
{
var client = new RestClient(BaseUrl) { Proxy = new WebProxy() };
var parameterisedUrl = new StringBuilder(MakeUrl(url));
foreach (var keyValuePair in additionalParameters)
{
parameterisedUrl.AppendFormat(@"&{0}={1}",
keyValuePair.Key, keyValuePair.Value);
}
var request = new RestRequest(parameterisedUrl.ToString());
var result = await client.ExecuteGetTaskAsync<T>(request);
return result.Data;
}
Since a Task can be easily translated to an IObservable<T> with a call to ToObservable(), I think it’s just easier and more flexible to leave this function returning a Task. With the use of async/await we remove some braces and make the code more readable.
The same is true when using this method to page through the action movies:
static async Task GetActionMovies()
{
var genreCollection =
await MakeMovieRequestAsync<GenreCollection>(@"genre/list");
var actionGenreId = (from genre in genreCollection.Genres
where genre.Name == "Action"
select genre.Id).First();
var actionMoviesUrl = string.Format(
@"genre/{0}/movies", actionGenreId);
var actionMovieCollection =
await MakeMovieRequestAsync<MovieCollection>(actionMoviesUrl);
var totalPages = actionMovieCollection.TotalPages;
for (int i = 1; i <= totalPages; i++)
{
var parameter = new KeyValuePair<string, string>(
"page", i.ToString(CultureInfo.InvariantCulture));
var movies = await MakeMovieRequestAsync<MovieCollection>(
actionMoviesUrl, parameter);
foreach (var movie in movies.Results)
{
Console.WriteLine("Movie {0}", movie.Title);
}
}
}
The use of async/await means this code tells a clear story here – there are no obscure uses of Rx features like Concat to constrain concurrency; the for loop makes it clear how the results are being pulled together.
There are plenty of examples where Rx makes sense – often when coordinating complex streams.
The Rx solution is actually quite involved with some concepts that are not particularly intention revealing. In my opinion, in this case it makes the solution harder to maintain compared with the async/await approach.
As I said at the beginning, I’m often guilty of this – its very easy to forget how baffling Rx can be to the uninitiated. It should be used only when it brings real advantages to the table.
You often don’t have to look too far for this – even an idea as simple as adding a Timeout and decent error handling can be surprising hard to express with the TPL and yet quite simple with Rx. Pre .NET 4.5 and async/await, having to chain tasks together the old way was also a real chore.
That said, I won’t be stopping my love affair with Rx anytime soon – there’s plenty more nails that need hammering in with it!