使用Rx和ObservableCollection进行线程安全

时间:2014-04-03 15:31:38

标签: c# wpf thread-safety observablecollection system.reactive

为了追求更快速响应的方式来更新具有大量项目的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(); (忘了错误,抱歉)。 我需要使用锁吗?

1 个答案:

答案 0 :(得分:1)

您的实现存在一些问题,其根源在于如何正确使用EnableCollectionSynchronization。说实话,我并不感到惊讶 - 它的记录很少,设计也不是很好(我怀疑主要是因为必须让它与WPF一起使用而不会破坏更改)。

正确使用EnableCollectionSynchronization

我将简要介绍正确的用法:

EnableCollectionSynchronization注册一个WPF将在需要访问集合时使用的锁(例如,当控件枚举它时)。

在.NET 4.5中,在后台线程上引发的集合更改事件(即不是调用EnableCollectionSynchronization的线程的线程)排队并编组到UI线程无论如何

重要的是,使用此方法时,您仍必须使用您在EnableCollectionSynchronization注册的锁定来锁定对该集合的任何访问权。特别是,您的代码中没有任何内容阻止Clear()Add()同时运行,这肯定是您所看到的异常的原因。

调用EnableCollectionSynchronization的好地方是从处理程序到BindingOperations.CollectionRegistering事件,这可以保证在创建任何CollectionView实例之前进行调用。

更新UI绑定集合的最佳实践

所有这一切,我相信你应该完全放弃这种方法 - 而更好来承诺只更新UI线程上的UI绑定集合。 RX非常适用于此,您可以使用ObserveOnsee here for a good primer)来更新UI线程。如果你这样做,你不再需要担心同步对集合的访问权限,你的生活将变得更加简单。

我的建议 - 完成所有工作,确定后台线程需要哪些更新,但修改UI线程本身的集合。这种方法几乎总是足够快,如果不是你可能需要考虑你的设计。

查看my answer here进行讨论并链接到有关UI更新的文章。

另请参阅this question,了解对UI线程中发生的大量更改的问题的另一个讨论/分析,以及如何缓冲大量更改以避免占用调度程序。

它还值得研究ReactiveUI框架。 See also here.