24

一次又一次地,我发现自己不得不编写线程安全版本的 BindingList 和 ObservableCollection,因为当绑定到 UI 时,这些控件不能从多个线程中更改。我想了解的是为什么会这样——是设计错误还是故意的?

4

4 回答 4

31

问题是设计一个线程安全的集合并不简单。当然,设计一个可以从多个线程修改/读取而不会破坏状态的集合很简单。但是设计一个可用的集合要困难得多,因为它是从多个线程更新的。以下面的代码为例。

if ( myCollection.Count > 0 ) {
  var x = myCollection[0];
}

假设 myCollection 是一个线程安全的集合,其中添加和更新保证不会破坏状态。此代码不是线程安全的,并且是竞争条件。

为什么?即使 myCollection 是安全的,也不能保证在对 myCollection 的两个方法调用之间不会发生更改:命名为 Count 和索引器。另一个线程可以进来并删除这些调用之间的所有元素。

坦率地说,这种类型的问题使使用这种类型的集合成为一场噩梦。您永远不能让一次调用的返回值影响对集合的后续调用。

编辑

我在最近的一篇博客文章中扩展了这个讨论:http: //blogs.msdn.com/jaredpar/archive/2009/02/11/why-are-thread-safe-collections-so-hard.aspx

于 2009-02-09T17:13:20.573 回答
6

为 Jared 的出色回答添加一点内容:线程安全并非免费提供。许多(大多数?)集合仅在单个线程中使用。为什么这些集合会有性能或功能损失来应对多线程情况?

于 2009-02-09T17:20:58.560 回答
5

从所有其他答案中收集想法,我认为这是解决您的问题的最简单方法:

改变你的问题:

“为什么X班不理智?”

“在 X 班上做这件事的明智方法是什么?”

  1. 在类的构造函数中,在创建可观察集合时获取当前调度程序。因为,正如您所指出的,需要在原始线程上进行修改,这可能不是GUI 线程。所以App.Current.Dispatcher并不总是正确的,并不是所有的类都有this.Dispatcher

    _dispatcher = System.Windows.Threading.Dispatcher.CurrentDispatcher;
    _data = new ObservableCollection<MyDataItemClass>();
    
  2. 使用调度程序调用需要原始线程的代码部分。

    _dispatcher.Invoke(new Action(() => { _data.Add(dataItem); }));
    

那应该对你有用。尽管在某些情况下您可能更喜欢.BeginInvoke而不是.Invoke

于 2012-11-14T23:48:54.990 回答
2

如果您想发疯 -这是一个ThreadedBindingList<T>自动在 UI 线程上返回通知的方法。但是,一次只有一个线程进行更新等仍然是安全的。

于 2009-02-09T20:21:24.473 回答