为了追求更快速响应的方式来更新具有大量项目的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(); (忘了错误,抱歉)。 我需要使用锁吗?
答案 0 :(得分:1)
您的实现存在一些问题,其根源在于如何正确使用EnableCollectionSynchronization。说实话,我并不感到惊讶 - 它的记录很少,设计也不是很好(我怀疑主要是因为必须让它与WPF一起使用而不会破坏更改)。
EnableCollectionSynchronization
我将简要介绍正确的用法:
EnableCollectionSynchronization
注册一个WPF将在需要访问集合时使用的锁(例如,当控件枚举它时)。
在.NET 4.5中,在后台线程上引发的集合更改事件(即不是调用EnableCollectionSynchronization
的线程的线程)排队并编组到UI线程无论如何。
重要的是,使用此方法时,您仍必须使用您在EnableCollectionSynchronization
注册的锁定来锁定对该集合的任何访问权。特别是,您的代码中没有任何内容阻止Clear()
与Add()
同时运行,这肯定是您所看到的异常的原因。
调用EnableCollectionSynchronization
的好地方是从处理程序到BindingOperations.CollectionRegistering
事件,这可以保证在创建任何CollectionView
实例之前进行调用。
所有这一切,我相信你应该完全放弃这种方法 - 而更好来承诺只更新UI线程上的UI绑定集合。 RX非常适用于此,您可以使用ObserveOn
(see here for a good primer)来更新UI线程。如果你这样做,你不再需要担心同步对集合的访问权限,你的生活将变得更加简单。
我的建议 - 完成所有工作,确定后台线程需要哪些更新,但修改UI线程本身的集合。这种方法几乎总是足够快,如果不是你可能需要考虑你的设计。
查看my answer here进行讨论并链接到有关UI更新的文章。
另请参阅this question,了解对UI线程中发生的大量更改的问题的另一个讨论/分析,以及如何缓冲大量更改以避免占用调度程序。
它还值得研究ReactiveUI框架。 See also here.