简体   繁体   中英

Asynchrony with TAP (async/await) in WPF MVVM

I am looking for a best practice to the following setup.

A View is data bound to ViewModel (through WPF). ViewModel updates View via INotifyPropertyChanged. Model updates ViewModel via events. The Model knows about stuff from the outside-world, like how to get stuff from the internet via WebClient, and how to retrieve data from a database.

Getting and sending stuff to the outside-world should be done asynchronously in order for the UI (and by extension, the user) not to suffer from waiting on the outside-world.

What would be the best practice way to go about this?

1. The ViewModel should be responsible for calling model methods asynchronously.
This would have the advantage of being able to write stuff like

GetWebPage(string url) {
  var result = await Model.GetWebPageAsync(url);
  Url = result.Url;
}

in the ViewModel where Url is a ViewModel property with INotifyPropertyChanged to update the View. Or even

GetWebPage(string url) {
  var result = await Model.GetWebPageAsync(url);
  _view.Url = result.Url;
}

where we can avoid the INotifyPropertyChanged all together. Which of these ways do you prefer?

However, it might be more sensible to have the Model itself do the asynchronous work. We might want to be able to use the Model without the View and ViewModel, while still having it work asynchronously. Another argument is that, who knows better that the Model what stuff is best handled asynchronously.

2. The Model handles all the asynchronous stuff by itself. The ViewModel code becomes more like

GetWebPage(string url) {
  Model.GetWebPage(url);
}

and in the Model

GetWebPage(string url) {
  var result = await Model.GetWebPageAsync(url);
  if (UrlChanged != null);
     UrlChanged(this, new UrlChangedEventArgs(url));
}

that the ViewModel can subscribe to, and update the View accordingly.

Which of the ways do you think is the best practice?

The third way: view model makes asynchronous call, but it uses client-side service to retrieve web page, model itself is anemic (it knows nothing about outer world):

GetWebPage(string url) 
{
  var dataService = anyServiceLocator.GetService<IDataService>();
  var result = await dataService.GetWebPageAsync(url, Model);
  Url = result.Url;
}

This allows to change real data downloading algorithm, eg for testing purposes.

I believe the best practice for the scenario described will be the one you'll come up with - considering pros and cons of every approach - and that will be specific to your task (as the requirements change from task to task). Regarding the actual answer to the question, I believe there is no much difference. Although I'm thinking another approach now. Think about parameterizing the asynchronous behavior of a Helper (maybe DownloadManager) object, which can be passed in to the VM as a parameter. This will allow you to test both the VM and the Model easily without distinguishing the behavior, and test that behavior separately on DownloadManager.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM