【问题标题】:Add items to a list from the thread that initialized it从初始化它的线程将项目添加到列表
【发布时间】:2015-09-14 11:45:53
【问题描述】:

我有一个通过 WCF 与服务器通信的 WPF 应用程序。 我在远程服务器上执行一个方法,回调方法初始化一个列表,其中包含在不同线程上运行的结果。 - 这很好,这正是我申请的目的。

但是当我想向这个列表中添加更多项目时,它会抛出一个异常,即我无法从已初始化此列表的不同线程中添加项目。

public ObservableCollection<ListBoxItemVM<T>> Items
{
    get { return items; }
    set
    {
        // This section runs on a separate thread.
        items = value;
        notify("Items");
        if (allItems == null)
            allItems = new ObservableCollection<ListBoxItemVM<T>>(items.Clone());

        // I want to save the current context here and use it on the AddItem method

        CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(Items);
        view.Filter = searchFilter;
    }
}

public void AddItem(ListBoxItemVM<T> 
{
    this.items.Add(item); // The following exception throws here
}

例外:这种类型的 CollectionView 不支持从不同于 Dispatcher 线程的线程更改其 SourceCollection。

我正在寻找某种方法来保存用于初始化列表的线程(或线程的 ExecuteContext),并使用此线程/上下文将项目添加到该列表中。

应该提到的是,与 UI 线程无关,我在代码中的另一个区域处理了 UI 线程的封送处理。 我尝试使用 UI SynchronizationContext 编组this.items.Add(item); 代码,但购买它们是不同的,所以它失败了。

谢谢

【问题讨论】:

  • 你也可以试试 'items.Dispatcher.Invoke' 而不是 'this'
  • 是的,你说得对,但我确实希望这部分代码在单独的线程上运行。谢谢。
  • 其实你可以在一些不同的对象中找到dispatcher,就像你之前做的this.Dispatcher,listbox.Dispatcher,还有Application.Current.Dispatcher。您是否尝试了所有这些并收到相同的错误消息?
  • 我可以在我的代码中的任何区域访问 UI 调度程序,因为我已经将它保存在一个通用静态类中,我在我的代码中的许多区域使用它,这不是问题。我这里不需要。问题是列表初始化的线程是 X 并且添加项方法在线程 Y 中运行,但是我希望 Y 在添加一些项时与 X 编组。感谢您的帮助

标签: c# wpf multithreading wcf task-parallel-library


【解决方案1】:

从 .NET 4.5 开始,有一个内置机制可以自动同步对集合的访问并将 CollectionChanged 事件分派到 UI 线程。要启用此功能,您需要在 UI 线程中调用 BindingOperations.EnableCollectionSynchronization

EnableCollectionSynchronization 做了两件事:

记住调用它的线程,并导致数据绑定管道在该线程上编组 CollectionChanged 事件。 在处理了编组的事件之前获取对集合的锁定,以便运行 UI 线程的事件处理程序在从后台线程修改集合时不会尝试读取集合。 非常重要的是,这并不能解决所有问题:为了确保对本质上不是线程安全的集合的线程安全访问,您必须与框架合作,在集合即将被修改时从后台线程获取相同的锁。

因此正确操作所需的步骤是:

  1. 决定你将使用什么样的锁定

    这将确定必须使用哪个 EnableCollectionSynchronization 重载。大多数情况下,一个简单的锁语句就足够了,因此这种重载是标准选择,但如果您使用一些花哨的同步机制,也可以支持自定义锁。

  2. 创建集合并启用同步

    根据选择的锁定机制,在 UI 线程上调用适当的重载。如果使用标准锁定语句,您需要提供锁定对象作为参数。如果使用自定义同步,您需要提供 CollectionSynchronizationCallback 委托和上下文对象(可以为 null)。调用时,此委托必须获取您的自定义锁,调用传递给它的 Action 并在返回之前释放锁。

  3. 通过在修改之前锁定集合来进行合作

    当您要自己修改集合时,您还必须使用相同的机制锁定集合;在简单场景中使用传递给 EnableCollectionSynchronization 的同一个锁对象上的 lock() 来执行此操作,或者在自定义场景中使用相同的自定义同步机制。

【讨论】:

  • 谢谢!这是很好的答案和描述。再次感谢你:)
猜你喜欢
  • 2021-06-02
  • 2015-02-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多