Wpf 从后台线程更新DataContext

Wpf 从后台线程更新DataContext,wpf,async-await,ui-thread,Wpf,Async Await,Ui Thread,我在后台线程中为wpf窗口获取数据,如下所示[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;

我在后台线程中为wpf窗口获取数据,如下所示[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
}
当我在GetInstances中包含GetGroups的操作时,UI会显示这些组。
当我在一个单独的操作中更新时,DataContext包含正确的组,但UI不显示它们

GetGroups()
方法中,我为组的
observedcollection
添加了
NotifyCollectionChangedAction.Reset
,这没有帮助。
更奇怪的是,我只对列表调用了一次
NotifyCollectionChangedAction.Reset
,但执行了三次,而列表有十项

我可以通过写作来解决这个问题:

DataContext = await Task.Factory.StartNew(() => serviceagent.GetGroups(instances));
但这是通过backgound进程更新DataContext和UI的常规方法吗?
实际上,我只想更新现有的DataContext而不重新设置它

编辑:
serviceagent.GetGroups(instances)
详细信息:

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();
}

似乎对什么是DataContext有点困惑。DataContext不是必须更新的特殊对象。它是对要绑定到UI的一个或多个对象的引用。每当您对这些对象进行更改时,都会通知UI(如果您实现了正确的接口)

因此,除非您显式更改DataContext,否则您的UI无法猜测您现在想要显示一组不同的对象

事实上,在您的代码中,没有理由设置DataContext两次。只需将其设置为要显示的最后一组对象。事实上,由于您处理相同的数据,因此没有理由使用两个任务:

async Task refresh()
{
    // returns object of type Instances
    DataContext=await Task.Factory.StartNew(() => {
             var instances = serviceagent.GetInstances();
             return serviceagent.GetGroups(instances);
    });
}
注意:


您不应该使用
异步void
签名。它只用于fire and forget事件处理程序,您不关心它们是否成功。原因是不能等待
async void
方法,因此没有人知道它是否成功。

在代码的
async
部分有几点很突出:

  • 我在我的MSDN文章中解释。总之,
    void
    async
    方法的一种不自然的返回类型,因此它有一些怪癖,特别是在异常处理方面
  • ,正如我在博客上解释的
  • 虽然不完全是必需的,但遵循以下指南是一个好主意:;遵循这些命名约定(etc)将有助于其他开发人员维护代码
基于这些,我也推荐我的博客文章

关于实际问题

从后台线程更新数据绑定代码总是很棘手的。我建议您将ViewModel数据视为UI的一部分(可以说,它是一个“逻辑UI”)。因此,在后台线程上检索数据是可以的,但是更新实际的VM值应该在UI线程上完成

这些更改使您的代码看起来更像这样:

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(),
  };
}

我发现
RaiseCollectionChanged
DataContext
绑定到的属性
Groups
没有影响。我只需要通知:
instances.RaisePropertyChanged(“组”)

如果XAML文件中的绑定正确,我认为问题出在GetGroups方法中。你能告诉我们这个方法吗?基本上代码就是
{instances.Groups=newviewmodelcollection(webcall);RaiseCollectionChanged();}
请把代码放在问题中,而不是注释中。同时添加任何缺失的相关代码。在哪里调用Reset?它与问题的其余部分有什么关系?async void正在工作,为什么我需要async任务?注意GetInstances()不是void方法,只有GetGroups()是void,我想我可以通过引用传递DataContext?感谢w.r.t.
TaskEx.Run的伟大建议,我不知道。您还看到my
PropertyChanged
中的缺陷。解决方案的一个缺点是需要引用UI项目中的所有数据契约。我只希望这些引用出现在我的ViewModel项目中,而不是
instances.Groups=newobservedcollection(groupResults.Groups)
在UI项目中,我在VM项目中做了完全相同的事情,然后是
RaisePropertyChanged(“Groups”)。这不是更像是MVVM,而不是在视图中设置列表吗?
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(),
  };
}
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;
}