I will show in this article an approach to make an asynchronous call to the model, to prevent the user interface to freeze. If you read about WPF, you will see there is a lot of information and claims “don’t freeze the ui thread”, “build UI more responsiveness” and so on.
NHibernate Sessions are not thread safe, so don’t use a single session in multiples threads. Conversation-per-Business-Transaction use the same session for a conversation. The end of the conversation flush the changes and close the session, the abort of the conversation discard changes and close the session.
You can’t update UI controls from a non-ui thread. Some people read this like “don’t set a ViewModel property from a non UI thread”. But this is not true, because it depends where do you raise the property changed notification thank to my friend German Schuager for reminding me this post from Rob Eisenberg. However, I’m not using this trick for now.
Asynchronous code is HARD to unit test. I will like to separate the asynchronous code in few units more testable and test “in-sync”.
The load of the artist list is very slow and this causes the user interface to freeze. This is very irritating for the end user.
I will break the async problem in the following three steps:
This my generic implementation of ICommand for make async calls.
public class AsyncCommandWithResult<TParameter, TResult> : IAsyncCommandWithResult<TParameter, TResult> { private readonly Func<TParameter, bool> _canAction; private readonly Func<TParameter, TResult> _action; public AsyncCommandWithResult(Func<TParameter, TResult> action) { _action = action; } public AsyncCommandWithResult( Func<TParameter, TResult> action, Func<TParameter, bool> canAction) { _action = action; _canAction = canAction; } public Action<TParameter, TResult> Completed { get; set; } public Action<TParameter> Preview { get; set; } public bool BlockInteraction { get; set; } public void Execute(object parameter) { //Execute Preview Preview((TParameter)parameter); //This is the async actions to take... worker.DoWork += (sender, args) => { args.Result = _action((TParameter)parameter); }; //When the work is complete, execute the postaction. worker.RunWorkerCompleted += (sender, args) => { Completed((TParameter)parameter, (TResult)args.Result); CommandManager.InvalidateRequerySuggested(); }; //Run the async work. worker.RunWorkerAsync(); } [DebuggerStepThrough] public bool CanExecute(object parameter) { if (BlockInteraction && worker.IsBusy) return false; return _canAction == null ? true : _canAction((TParameter)parameter); } public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } public TResult ExecuteSync(TParameter obj) { return _action(obj); } private static readonly BackgroundWorker worker = new BackgroundWorker(); }
Here you can see the test of the three steps. None of these test involves asynchronous calls.
[Test] public void preview_of_load_list_should_show_status_info() { var browseArtistVm = new BrowseArtistViewModel( new Mock<IBrowseArtistModel>().Object, new Mock<IViewFactory>().Object); browseArtistVm.LoadListCommand.Preview(null); browseArtistVm.Status.Should().Be.EqualTo("Loading artists..."); } [Test(Description = "Check if the process call the model")] public void load_list_command_should_load_artists_coll() { var artistModel = new Mock<IBrowseArtistModel>(); var artists = new List<Artist> {new Artist {Name = "Jose"}}; artistModel.Setup(am => am.GetAllArtists()).Returns(artists); var browseArtistVm = new BrowseArtistViewModel( artistModel.Object, new Mock<IViewFactory>().Object); browseArtistVm.LoadListCommand.ExecuteSync(null); artistModel.VerifyAll(); } [Test] public void completed_of_load_l_should_load_the_list_and_change_status() { var browseArtistVm = new BrowseArtistViewModel( new Mock<IBrowseArtistModel>().Object, new Mock<IViewFactory>().Object); var artists = new List<Artist>(); browseArtistVm.LoadListCommand.Completed(null, artists); browseArtistVm.Artists.Should().Be.SameInstanceAs(artists); browseArtistVm.Status.Should().Be.EqualTo("Finished"); }
The LoadListCommand of the ViewModel is:
public virtual IAsyncCommandWithResult<object, IList<Artist>> LoadListCommand { get { if (_loadListCommand == null) { _loadListCommand = new AsyncCommandWithResult<object, IList<Artist>> (o => _browseArtistModel.GetAllArtists()) { BlockInteraction = true, Preview = o => Status = "Loading artists...", Completed = (o, artists) => { Artists = artists; Status = "Finished"; } }; } return _loadListCommand; } }
You must to remember that an NHibernate Session should be used in only one thread. This model for Browsing Artists has only one method with EndMode = End. This means session-per-call, so each time I click the LoadCommand the model start a new conversation and session. If you have a ViewModel with multiples operations within the same Conversation better you use something else, or use AsyncCommand everywhere within the VM.
There are a lot of alternatives to this approach, here are some;
Use this only when you need it. You don’t have to do this everywhere. Some operations are very fast and inexpensive.
Divide and conquer; I really like this way of testing. Don’t bring asynchronous things to your unit tests.