Still getting my head around await/async, and am having difficulty seeing how I use it in an MVVM setting. I'm using MvvmLight, but I imagine the question applies to any other MVVM framework.
Suppose I have a WPF application that shows a list of pets. I have a RefreshCommand, that refreshes the list from a WCF service. The UI has an indicator to show that it's busy, and this is bound to an IsBusy bool property on the view model. My command code currently looks like this...
BackgroundWorker bw = new BackgroundWorker();
IsBusy = true;
bw.DoWork += (_, __) => {
Pets = _petService.GetPets();
IsBusy = false;
};
bw.RunWorkerAsync();
Pets is an ObservableCollection property on the view model.
I want to use the await/async keywords, and do away with the BackgroundWorker. I've searched and read, and read and searched, and am getting confused. Most of the articles/blog posts/etc I've seen seem to require you to create your own async implementation of ICommand. Why is this necessary? Given that MVVM and ICommand are built-in to the .NET framework, surely the ability to use async/await with them should also be built-in, without the need to create our own async command class.
Have I missed something, or is this really the only way to do it?
I tried the following, but it didn't work...
private async void LoadAsyncData() {
IsBusy = true;
Task<ObservableCollection<Pet>> t = GetPetsAsync();
Pets = await t;
IsBusy = false;
}
private async Task<ObservableCollection<Pet>> GetPetsAsync() {
await Task.Delay(1);
return _petService.GetPets();
}
I added the call to Task.Delay(1) as the compiler warned me that the method would be called sync.
The data loaded, but the UI never responded to the IsBusy property being changed, so the user doesn't see that the UI is busy.
Anyone able to advise?
You better listen to what compiler warns you about instead of getting that under the carpet :) After that useless await Task.Delay
method will continue on UI thread and so it's basically synchronous method still - that is why UI did not respond to IsBusy
property, it was blocked. Instead you have to either
Explicitly run your _petService.GetPets
on background thread:
private async void LoadAsyncData() {
IsBusy = true;
Pets = await Task.Run(() => _petService.GetPets());
IsBusy = false;
}
(better) Change your petService by adding asynchronous version of GetPets:
private async void LoadAsyncData() {
IsBusy = true;
Pets = await _petService.GetPetsAsync();
IsBusy = false;
}
Still getting my head around await/async, and am having difficulty seeing how I use it in an MVVM setting.
I have a three-part article series on async
MVVM that may help: async
data binding, async
commands, and async
services.
I want to use the await/async keywords, and do away with the BackgroundWorker.
If you just want to do the easier way of replacing BGW with Task.Run
, I have a blog post series on doing just that.
Most of the articles/blog posts/etc I've seen seem to require you to create your own async implementation of ICommand. Why is this necessary?
It's not required, but it is a pattern that I (and others) have found useful. It ensures your async MVVM commands are unit testable and properly callable from other code.
Given that MVVM and ICommand are built-in to the .NET framework, surely the ability to use async/await with them should also be built-in, without the need to create our own async command class.
There's no IAsyncCommand
in the .NET Framework. I believe it provides a useful abstraction, but it does not follow that that abstraction should be included in every MVVM application.
I added the call to Task.Delay(1) as the compiler warned me that the method would be called sync.
No, that's just avoiding the compiler warning. What you really want to do is fix the warning by making the method asynchronous. Evk's answer has the appropriate code for both approaches.
The BackgroundWorker
simulates asynchrony by synchronously calling WCF service on a background thread.
In your following example, although the the delay is asynchronous, you are back on the UI thread calling the service synchronously.
private async Task<ObservableCollection<Pet>> GetPetsAsync() {
await Task.Delay(1);
return _petService.GetPets(); //synchronous WCF call on UI thread - UI will freeze.
}
What you are missing is true asynchronous WCF client call - which you must generate while creating the WCF proxy. You must choose to 'Allow generation of asynchronous operation' and 'Generate task-based operations'. See step 6 here for reference.
Once you have the proxy right, you can use the asynchronous version like this:
private async void LoadAsyncData() {
IsBusy = true;
Pets = await _petService.GetPetsAsync(); // true async
IsBusy = false;
}
private async void LoadAsyncData() {
IsBusy = true;
var Pets = await GetPetsAsync();
IsBusy = false;
}
private async Task<ObservableCollection<Pet>> GetPetsAsync() {
var pets = await Task<ObservableCollection<Pet>>.Run(()=>{
return _petService.GetPets();
});
return pets;
}
In the method GetPetsAsync, a Task is started which may be awaited as shown. If you set a variable (var pets) you are performing a closure. When this is done, then pets is the result of the observable collection and the system will not return until the task is eithr done or faulted. Your petService as shown is synchronous.
If lots of your commands are async, try ReactiveUI. You can use just its ICommand implementation, which takes care of everything: disabling command execution while it's running, marshaling results bak to UI thread etc.
Then it would look like that (in SubViewModel constuctor):
Pets = new ReactiveList<Pet>();
GetPets = ReactiveCommand.Create<IEnumerable<Pet>>(async _ => {
return await _service.GetPets();
});
GetPets.Subscribe(pets => {
using(Pets.SuppressChangeNotifications()) // it's much easier to have one list and just change the items
{
Pets.Clear();
foreach(var pet in pets)
Pets.Add(pet);
}
});
GetPets.ThrownExceptions.Subscribe(ex =>{
// show error to user, retry etc
})
_isBusy = GetPets.IsExecuting.ToProperty(this, x => x.IsBusy);
// property definition
bool IsBusy => _isBusy?.Value ?? false;