I fetch data for a wpf window in a backgroundthread like this [framework 4.0 with async/await]:
async void refresh()
{
// returns object of type Instances
DataContext = await Task.Factory.StartNew(() => serviceagent.GetInstances());
var instances = DataContext as Instances;
await Task.Factory.StartNew(() => serviceagent.GetGroups(instances));
// * problem here * instances.Groups is filled but UI not updated
}
When I include the actions of GetGroups in GetInstances the UI shows the groups.
When I update in a seperate action the DataContext includes the groups correclty but the UI doesn't show them.
In the GetGroups() method I inlcuded NotifyCollectionChangedAction.Reset for the ObservableCollection of groups and this doesn't help.
Extra strange is that I call NotifyCollectionChangedAction.Reset on the list only once, but is executed three times, while the list has ten items?!
I can solve the issue by writing:
DataContext = await Task.Factory.StartNew(() => serviceagent.GetGroups(instances));
But is this the regular way for updating DataContxt and UI via a backgound process?
Actually I only want to update the existing DataContext without setting it again?
EDIT: serviceagent.GetGroups(instances) in more detail:
public void GetGroups(Instances instances)
{
// web call
instances.Admin = service.GetAdmin();
// set groups for binding in UI
instances.Groups = new ViewModelCollection<Groep>(instances.Admin.Groups);
// this code has no effect
instances.Groups.RaiseCollectionChanged();
}
Here ViewModelCollection<T> inherits from ObservableCollection<T> and I added the method:
public void RaiseCollectionChanged()
{
var handler = CollectionChanged;
if (handler != null)
{
Trace.WriteLine("collection changed");
var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
handler(this, e);
}
}
There's a few points that stand out in the async portion of your code:
async void in my MSDN article. In summary, void is an unnatural return type for async methods, so it has some quirks, particularly around exception handling.TaskEx.Run over StartNew for asynchronous tasks, as I explain on my blog.Based on these, I also recommend my intro to async blog post.
On to the actual problem...
Updating data-bound code from background threads is always tricky. I recommend that you treat your ViewModel data as though it were part of the UI (it is a "logical UI", so to speak). So it's fine to retrieve data on a background thread, but updating the actual VM values should be done on the UI thread.
These changes make your code look more like this:
async Task RefreshAsync()
{
var instances = await TaskEx.Run(() => serviceagent.GetInstances());
DataContext = instances;
var groupResults = await TaskEx.Run(() => serviceagent.GetGroups(instances));
instances.Admin = groupResults.Admin;
instances.Groups = new ObservableCollection<Group>(groupResults.Groups);
}
public GroupsResult GetGroups(Instances instances)
{
return new GroupsResult
{
Admin = service.GetAdmin(),
Groups = Admin.Groups.ToArray(),
};
}
The next thing you need to check is whether Instances implements INotifyPropertyChanged. You don't need to raise a Reset collection changed event when setting Groups; since Groups is a property on Instances, it's the responsibility of Instances to raise INotifyPropertyChanged.PropertyChanged.
Alternatively, you could just set DataContext last:
async Task RefreshAsync()
{
var instances = await TaskEx.Run(() => serviceagent.GetInstances());
var groupResults = await TaskEx.Run(() => serviceagent.GetGroups(instances));
instances.Admin = groupResults.Admin;
instances.Groups = new ObservableCollection<Group>(groupResults.Admin.Groups);
DataContext = instances;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With