【发布时间】:2009-02-09 17:05:28
【问题描述】:
我一次又一次地发现自己不得不编写 BindingList 和 ObservableCollection 的线程安全版本,因为当绑定到 UI 时,这些控件不能从多个线程中更改。我想了解的是为什么会出现这种情况 - 是设计错误还是故意的?
【问题讨论】:
标签: c# .net thread-safety observablecollection bindinglist
我一次又一次地发现自己不得不编写 BindingList 和 ObservableCollection 的线程安全版本,因为当绑定到 UI 时,这些控件不能从多个线程中更改。我想了解的是为什么会出现这种情况 - 是设计错误还是故意的?
【问题讨论】:
标签: c# .net thread-safety observablecollection bindinglist
问题在于设计一个线程安全的集合并不简单。当然,设计一个可以从多个线程修改/读取而不会破坏状态的集合很简单。但是设计一个可用的集合要困难得多,因为它是从多个线程更新的。以下面的代码为例。
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
【讨论】:
IList<T> 功能的许多有用子集可以以线程安全的方式实现。移除东西或移动东西的项目会有问题,但界面的其余部分将是许多应用程序的有用子集。报告添加项目索引的Add 版本会有所帮助,但并非所有应用程序都需要它。如果每个添加的项目在列表的生命周期内都将继续存在于同一个槽中,那么线程安全的IList<T> 实现在许多没有外部锁定的多线程场景中可能很有用。
为 Jared 的出色回答补充一点:线程安全并非免费提供。许多(大多数?)集合仅在单个线程中使用。为什么这些集合会有性能或功能损失来应对多线程情况?
【讨论】:
从所有其他答案中收集想法,我认为这是解决您的问题的最简单方法:
从以下位置更改您的问题:
“为什么 X 班不健全?”
到
“对 X 类执行此操作的合理方法是什么?”
在您的类的构造函数中,在您创建时获取当前调度程序 你的可观察集合。因为,正如您所指出的,修改需要 在 original 线程上完成,它可能不是 main GUI 线程。 所以 App.Current.Dispatcher 并不总是正确的, 并不是所有的类都有一个this.Dispatcher。
_dispatcher = System.Windows.Threading.Dispatcher.CurrentDispatcher;
_data = new ObservableCollection<MyDataItemClass>();
使用调度程序调用您的代码段 需要原始线程。
_dispatcher.Invoke(new Action(() => { _data.Add(dataItem); }));
这应该对你有用。尽管在某些情况下您可能更喜欢 .BeginInvoke 而不是 .Invoke。
【讨论】:
如果你想发疯 - here's ThreadedBindingList<T> 会自动在 UI 线程上返回通知。但是,一次只有一个线程进行更新等仍然是安全的。
【讨论】: