【问题标题】:Update DataContext from background thread从后台线程更新 DataContext
【发布时间】:2013-11-23 01:47:48
【问题描述】:

我在这样的后台线程中为 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() 方法中,我将NotifyCollectionChangedAction.Reset 包含在ObservableCollection 组中,但这没有帮助。
更奇怪的是,我在列表中只调用了一次NotifyCollectionChangedAction.Reset,却被执行了3次,而列表有十项?!

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

DataContext = await Task.Factory.StartNew(() => serviceagent.GetGroups(instances));

但这是通过后台进程更新 DataContxt 和 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();
}

这里ViewModelCollection&lt;T&gt;继承自ObservableCollection&lt;T&gt;,我添加了方法:

public void RaiseCollectionChanged()
{
    var handler = CollectionChanged;
    if (handler != null)
    {
        Trace.WriteLine("collection changed");
        var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
        handler(this, e);
    }
}

【问题讨论】:

  • 如果 XAML 文件中的绑定正确完成,我认为问题出在 GetGroups 方法中。你能告诉我们这个方法吗?
  • 基本上代码就是{ instances.Groups = new ViewModelCollection&lt;Group&gt;(webcall); RaiseCollectionChanged(); }
  • 请在问题中输入代码,而不是 cmets。还要添加任何缺少的相关代码。你在哪里调用重置,它与问题的其余部分有什么关系?

标签: wpf async-await ui-thread


【解决方案1】:

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

基于这些,我也推荐我的intro to async博文。

关于实际问题...

从后台线程更新数据绑定代码总是很棘手。我建议您将 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(),
  };
}

接下来需要检查的是Instances 是否实现了INotifyPropertyChanged。设置Groups 时不需要引发Reset 集合更改事件;因为GroupsInstances 上的一个属性,所以Instances 有责任提升INotifyPropertyChanged.PropertyChanged

或者,您可以只设置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;
}

【讨论】:

  • 感谢 w.r.t 的好建议。 TaskEx.Run,我不知道。您还看到了我的PropertyChanged 中的缺陷。您的解决方案的一个缺点是它需要引用 UI 项目中的所有数据合同。我只希望在我的 ViewModel 项目中使用这些引用。
  • 因此,我在 VM 项目中执行完全相同的操作,而不是 UI 项目中的 instances.Groups = new ObservableCollection&lt;Group&gt;(groupResults.Groups);,然后是 RaisePropertyChanged("Groups");。这不是更符合例如MVVM 而不是在 View 中设置列​​表?
【解决方案2】:

似乎对什么是 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);
    });
}

注意:

您不应该使用async void 签名。它仅用于即发即弃的事件处理程序,您不关心它们是成功还是失败。原因是无法等待async void 方法,因此没有人知道它是否成功。

【讨论】:

  • async void 正在工作,为什么我需要异步任务?请注意,GetInstances() 不是 void 方法,只有 GetGroups() 是 void,我想我可以通过引用传递 DataContext?
【解决方案3】:

我发现RaiseCollectionChangedDataContext 绑定的属性Groups 没有影响。我只需要通知:instances.RaisePropertyChanged("Groups");

【讨论】:

    猜你喜欢
    • 2012-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多