我在哪里可以获得线程安全的CollectionView?

Jon*_*len 68 .net wpf multithreading thread-safety collectionview

在后台线程上更新业务对象的集合时,我收到以下错误消息:

这种类型的CollectionView不支持从与Dispatcher线程不同的线程更改其SourceCollection.

好的,这是有道理的.但它也引出了一个问题,什么版本的CollectionView支持多个线程,如何让我的对象使用它?

小智 87

使用:

System.Windows.Application.Current.Dispatcher.Invoke(
    System.Windows.Threading.DispatcherPriority.Normal,
    (Action)delegate() 
    {
         // Your Action Code
    });
Run Code Online (Sandbox Code Playgroud)

  • 使用`Invoke`会导致UI冻结.请改用"BeginInvoke". (8认同)

Nat*_*ips 64

以下是对Jonathan发现的实现的改进.首先,它在与之关联的调度程序上运行每个事件处理程序,而不是假设它们都在同一个(UI)调度程序上.其次,它使用BeginInvoke来允许在我们等待调度程序可用时继续处理.这使得解决方案在后台线程通过每个更新之间进行大量更新的情况下更快.也许更重要的是,它克服了在等待Invoke时由阻塞引起的问题(例如,当使用带有ConcurrencyMode.Single的WCF时可能发生死锁).

public class MTObservableCollection<T> : ObservableCollection<T>
{
    public override event NotifyCollectionChangedEventHandler CollectionChanged;
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        NotifyCollectionChangedEventHandler CollectionChanged = this.CollectionChanged;
        if (CollectionChanged != null)
            foreach (NotifyCollectionChangedEventHandler nh in CollectionChanged.GetInvocationList())
            {
                DispatcherObject dispObj = nh.Target as DispatcherObject;
                if (dispObj != null)
                {
                    Dispatcher dispatcher = dispObj.Dispatcher;
                    if (dispatcher != null && !dispatcher.CheckAccess())
                    {
                        dispatcher.BeginInvoke(
                            (Action)(() => nh.Invoke(this,
                                new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))),
                            DispatcherPriority.DataBind);
                        continue;
                    }
                }
                nh.Invoke(this, e);
            }
    }
}
Run Code Online (Sandbox Code Playgroud)

因为我们使用的是BeginInvoke,所以在调用处理程序之前,通知的更改可能会被撤消.这通常会导致"指数超出范围".根据列表的新(已更改)状态检查事件参数时抛出异常.为了避免这种情况,所有延迟事件都将被重置事件替换.在某些情况下,这可能会导致过度重绘.

  • @guilhermecgs:我猜MultiThreaded (3认同)

Cam*_*and 17

Bea Stollnitz的这篇文章解释错误信息及其原因.

编辑:来自Bea的博客

不幸的是,这段代码导致异常:"NotSupportedException - 这种类型的CollectionView不支持从与Dispatcher线程不同的线程更改其SourceCollection."我理解这条错误消息会让人们认为,如果CollectionView它们是使用不支持跨线程更改,然后他们必须找到那个.好吧,这个错误消息有点误导:我们提供的开箱即用的CollectionViews都不支持跨线程集合更改.不,不幸的是,我们无法在此时修复错误消息,我们非常关注.


Jon*_*len 7

找到一个.

public class MTObservableCollection<T> : ObservableCollection<T>
{
   public override event NotifyCollectionChangedEventHandler CollectionChanged;
   protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
   {
      var eh = CollectionChanged;
      if (eh != null)
      {
         Dispatcher dispatcher = (from NotifyCollectionChangedEventHandler nh in eh.GetInvocationList()
                 let dpo = nh.Target as DispatcherObject
                 where dpo != null
                 select dpo.Dispatcher).FirstOrDefault();

        if (dispatcher != null && dispatcher.CheckAccess() == false)
        {
           dispatcher.Invoke(DispatcherPriority.DataBind, (Action)(() => OnCollectionChanged(e)));
        }
        else
        {
           foreach (NotifyCollectionChangedEventHandler nh in eh.GetInvocationList())
              nh.Invoke(this, e);
        }
     }
  }
}
Run Code Online (Sandbox Code Playgroud)

http://www.julmar.com/blog/mark/2009/04/01/AddingToAnObservableCollectionFromABackgroundThread.aspx

  • 请注意,这将导致每个集合更改的线程切换,并且所有更改都被序列化(这违背了具有后台线程的目的:-)).对于少数几个项目并不重要,但如果你计划添加很多项目,那将会对性能产生很大影响.我通常将项目添加到后台线程中的另一个集合中,然后将它们移动到计时器上的gui集合中. (3认同)