ReactiveXaml Series: ReactiveAsyncCommand
Motivation
If you’ve done any WPF programming that does any sort of interesting work, you know that one of the difficult things is that if you do things in an event handler that take a lot of time, like reading a large file or downloading something over a network, you will quickly find that you have a problem: during the blocking call, the UI turns black. Silverlight heads this off at the pass – you can’t even do blocking operations at all.
So you say, “Oh, I’ll just run it on another thread!” Then, you find the 2nd tricky part – WPF and Silverlight objects have thread affinity. Meaning, that you can only access objects from the thread that created them. So, at the end of the computation when you go to run textBox.Text = results;, you suddenly get an Exception.
Dispatcher.BeginInvoke solves this
So, once you dig around on the Internet a bit, you find out the pattern to solve this problem involves the Dispatcher:
{
var some_data = this.SomePropertyICanOnlyGetOnTheUIThread;
var t = new Task(() => {
var result = doSomethingInTheBackground(some_data);
Dispatcher.BeginInvoke(new Action(() => {
this.UIPropertyThatWantsTheCalculation = result;
}));
}
t.Start();
}
We use this pattern a lot, let’s make it more succinct
So, the idea of ReactiveAsyncCommand is that many times, when we run a command, we often are just:
- The command executes, we kick off a thread
- We calculate something that takes a long time
- We take the result, and set a property on the UI thread, using Dispatcher
Because we encapsulate the pattern, we can get other stuff for free
ReactiveAsyncCommand attempts to capture that pattern, and makes certain things easier. For example, you often only want one async instance running, and the Command should be disabled while we are still processing. Another common thing you would want to do is, display some sort of UI while an async action is running – something like a spinner control or a progress bar being displayed.
Since ReactiveAsyncCommand derives from ReactiveCommand, it does everything its base class does – you can use it identically, and the Execute IObservable tells you when workitems are queued.. What ReactiveAsyncCommand does that would be hard to do with ReactiveCommand directly, is that it has code built-in to automatically keep track of in-flight workitems.
The first pattern – running an Action in the background
Here’s a simple use of a Command, who will run a task in the background, and only allow one at a time (i.e. its CanExecute will return false until the action completes)
cmd.RegisterAsyncAction(i => {
Thread.Sleep((int)i * 1000); // Pretend to do work
};
cmd.Execute(5 /*seconds*/);
cmd.CanExecute(5); // False! We’re still chewing on the first one.
Putting it all together
Remember, ReactiveXaml is a MVVM framework – to fully demonstrate calculating a value and displaying it in the UI, it’s easiest to make a very simple MVVM application. Here’s the XAML, and the basic class:
x:Name="Window" Height="350" Width="525">
<grid DataContext="{Binding ViewModel, ElementName=Window}">
<stackpanel HorizontalAlignment="Center" VerticalAlignment="Center">
<textblock Text="{Binding DataFromTheInternet}" FontSize="18"/>
<button Content="Click me!" Command="{Binding GetDataFromTheInternet}"
CommandParameter="5" MinWidth="75" Margin="0,6,0,0"/>
</stackpanel>
</grid>
</window>
And the codebehind:
using System.Threading;
using System.Windows;
using ReactiveXaml;
namespace RxBlogTest
{
public partial class MainWindow : Window
{
public AppViewModel ViewModel { get; protected set; }
public MainWindow()
{
ViewModel = new AppViewModel();
InitializeComponent();
}
}
public class AppViewModel : ReactiveValidatedObject
{
ObservableAsPropertyHelper[string] _DataFromTheInternet;
public string DataFromTheInternet {
get { return _DataFromTheInternet.Value; }
}
public ReactiveAsyncCommand GetDataFromTheInternet { get; protected set; }
}
}
This is a simple MVVM application, whose ViewModel has two items – a Command called “GetDataFromTheInternet”, and a place to store the results, a property called “DataFromTheInternet”. I’ll describe ObservableAsPropertyHelper later, but you can think of it as a class that “Remembers the latest value of an IObservable”.
Using ReactiveAsyncCommand
The difference between RegisterAsyncAction and RegisterAsyncFunction is the return value. The latter function returns an IObservable representing the results that will be returned. For async calls, you can often think of IObservable as a Future Result, that is, a “promise” of a result (or an Exception if something goes pear-shaped). In this case, our IObservable represents the “output” pipeline, and will produce results every time someone fires the command, one per Execute().
Here’s how we actually implement the async function, in the ViewModel constructor.
{
GetDataFromTheInternet = new ReactiveAsyncCommand(null, 1 /*at a time*/);
//
// This function will return a "stream" of results, one per invocation
// of the Command
//
var future_data = GetDataFromTheInternet.RegisterAsyncFunction(i => {
Thread.Sleep(5 * 1000); // This is a pretend async query
return String.Format("The Future will be {0}x as awesome!", i);
});
// OAPH will "watch" future_data, and raise property changes when new values
// come in. It’ll also provide the latest result that came in.
_DataFromTheInternet = new ObservableAsPropertyHelper<string>(future_data,
x => RaisePropertyChanged("DataFromTheInternet"));
}
</string>
Why is this cool?
Notice what I didn’t have to do here: I didn’t have to use any sort of explicit async mechanism like a Task or a new Thread, I didn’t have to marshal data back to the UI thread using Dispatcher.BeginInvoke, and my code reads way more like a simple, single-threaded application again, instead of chaining async invocations. Stuff like this is why I’m really excited about some of the concepts in ReactiveXaml.
Furthermore, there’s something else here that’s very motivating: testability. Using Dispatcher.BeginInvoke means that we’re assuming that a Dispatcher exists and works. If you’re in a unit test runner, this isn’t true. Which means, your Commanding code if you’re using other MVVM frameworks that don’t handle this isn’t testable. ReactiveXaml automatically detects whether you are in a test runner, and changes its default IScheduler to not use the Dispatcher. Test code still works, without hacking your ViewModel code at all.
Where’s the Code?
Get the code here: ReactiveAsyncCmd.zip. Also, I’m too lazy to correct the typo in the namespace, bt it doesn’t matter. Thoughts? Comments? Ideas?
