为了追求更灵敏的方式来更新具有大量项目的ListBox,我转向了Rx。这是我的实现:
ObservableCollection<FileData> _fileCollection = new ObservableCollection<FileData>();
public ObservableCollection<FileData> FileCollection { get { return _fileCollection; } }
public static object _fileCollectionLock = new object();
public Subject<FileData> subject = new Subject<FileData>();
public MainWindow( )
{
InitializeComponent();
BindingOperations.EnableCollectionSynchronization(_fileCollection, _fileCollectionLock);
UpdateFileList(subject);
}
private void UpdateFileList(IObservable<FileData> sequence)
{
sequence.Subscribe(value=>_fileCollection.Add(value));
}
private void ListFiles(string fullpath)
{
_fileCollection.Clear(); //crashed once
Task.Factory.StartNew(() =>
{
DirectoryInfo info = new DirectoryInfo(fullpath);
IEnumerable files = info.EnumerateFiles(Filter + "*", SearchOption.TopDirectoryOnly,true);
foreach (FileInfo file in files)
{
...
FileData fd = new FileData(filename, filedate, filesize, fileext);
subject.OnNext(fd);
曾经在我的代码中崩溃了 _fileCollection.Clear();(忘记错误,抱歉)。我需要使用锁吗,在哪里?
您的实现存在几个问题,根源在于对如何正确使用 EnableCollectionSynchronization 的误解。老实说,我并不感到惊讶 - 它的文档很差,设计也不是很好(我怀疑主要是因为必须让它与 WPF 一起工作而不会中断更改)。
正确使用EnableCollectionSynchronization
我将简要介绍正确的用法:
EnableCollectionSynchronization
注册一个锁,WPF 将在需要访问集合时(例如,当控件枚举集合时)使用该锁。
在 .NET 4.5 中,在后台线程(即不是从何处调用EnableCollectionSynchronization
的线程的线程)上引发的集合更改事件无论如何都会排队并封送到 UI 线程。
重要的是,使用此方法时,您仍然必须使用您在EnableCollectionSynchronization
中注册的相同锁自己锁定对集合的任何访问。特别是,代码中没有任何内容阻止Clear()
与Add()
同时运行,这肯定是导致您看到的异常的原因。
调用EnableCollectionSynchronization
的好位置是从处理程序到 BindingOperations.CollectionRegistering
事件,这保证在创建任何CollectionView
实例之前进行调用。
更新 UI 绑定集合的最佳做法
综上所述,我认为您应该完全放弃这种方法 - 最好只更新 UI 线程上的 UI 绑定集合。RX 非常适合此,您可以使用ObserveOn
(请参阅此处以获取良好的入门)来编组对 UI 线程的更新。如果您这样做,则不再需要担心同步对集合的访问,您的生活将变得更加简单。
我的建议 - 完成所有工作,以确定后台线程上需要哪些更新,但修改 UI 线程本身的集合。这种方法几乎总是足够快,如果不是,您可能需要考虑您的设计。
请在此处查看我的回答以进行讨论,并链接到有关UI更新的文章。
另请查看此问题,以再次讨论/分析UI线程上发生大量更改的问题,以及如何缓冲大量更改以避免占用调度程序。
ReactiveUI框架也值得一看。另请参阅此处。