In my WPF application which follows MVVM, I have the following code in my view model. What it does is quite simply open up a window which has a DataGrid by populating some data in it.
This works as it is but the problem is with performance. More the no. of items to be loaded increases, more time it takes to execute this OpenWindow() method, hence it takes a long time for the window to open. This can be quite frustrating from a users point where he/she clicks the button and its takes a good 15-20 seconds until something appears on screen.
I'm trying to map the concepts of C#'s Async/ Await as a solution to my problem but would really appreciate some direction as I'm fairly new to this.
The ideal solution would be to Open up even an empty window ASAP on click ( that means that the OpenWindow() method will complete execution while the methods inside of it are running in parallel) and eventually add in the required data ( which comes from the 2 methods GetData and AddData). My code is below, any ideas of suggestions would be very helpful
public async Task OpenWindow()
{
// A window with some data would open by the end of executing this method
await GetData();
// After the above has successfully executed I have all the data I need
AddData(Items);
}
private async Task<IEnumerable<ContentObject>> GetData()
{
// Quite a time consuming server call, something like
var result = await Server.Call(z => z.GetData(request));
return result? .ToList();
// Needs to execute prior to AddData method from the caller since it get some data to be added
}
private void AddData(IEnumerable<ItemType> items)
{
AllItemList = new ObservableCollection<ItemModel>();
// Adds each item to an observable collection which is intended to populate a datagrid
// Some items are from the IEnumerable<ItemType> items but some data are obtained from the server call in the previous method which is why its awaited
// It is OK that this method carries out operations after the window has been opened.
foreach (var item in items)
{
var currentItem = new ItemModel
{
ID = item.id,
Name = item.Name
};
AllItemList.Add(currentTask);
this.NotifyPropertyChanged("AllItemList");
}
}
CodePudding user response:
The ideal solution would be to Open up even an empty window ASAP on click ( that means that the OpenWindow() method will complete execution while the methods inside of it are running in parallel) and eventually add in the required data ( which comes from the 2 methods GetData and AddData).
I recommend using the technique from my article on Asynchronous MVVM: Data Binding (updated code is here). Then you have something like this:
public NotifyTask<ObservableCollection<ItemModel>> AllItemList { get; private set; }
public MyWindow()
{
AllItemList = NotifyTask.Create(async () => AddData(await GetData()));
}
private ObservableCollection<ItemModel> AddData(IEnumerable<ItemType> items)
{
var result = new ObservableCollection<ItemModel>();
foreach (var item in items)
{
var currentItem = new ItemModel
{
ID = item.id,
Name = item.Name
};
result.Add(currentTask);
}
return result;
}
NotifyTask<T> has properties you can bind to, including Result (a T value), IsSuccessfullyCompleted, IsFaulted, and ErrorMessage.
CodePudding user response:
Using TPL (tasks, async, await) to fetch data asynchronously is a great idea to improve your WPF app. However, be aware that changes to your view model need to be made from the main thread, otherwise your databinding will not work. You can use the application Dispatcher to invoke your AddData function on the main thread; however, be sure that the main thread is not blocked waiting on one of your async methods to complete.
This blog has some great examples of ways to use the Dispatcher to ensure that your AllItemList is updated from the main thread. It could look something like this:
private void AddData(IEnumerable<ItemType> items)
{
// this code will be run on the UI thread as soon as it is available
Dispatcher.BeginInvoke((Action) (() =>
{
AllItemList = new ObservableCollection<ItemModel>();
// ...
this.NotifyPropertyChanged("AllItemList");
}));
}
