返回首页

{S0}简介
本文提供了一个描述我是如何实现一个字典类型的类,可以绑定到一个WPF列表控制,但可以从一个线程,不WPF用户界面线程更新。这是有用的,当你的工作线程更新词典和您要绑定到控件的字典的内容。微软确实提供了一些NET 4.0的ObservableCollection类,但不幸的是ObservableDictionary是不是其中之一,也是他们提供的ObservableCollection类不是多线程友好。虽然我在本文中提供的类不是像新的。NET 4.0 System.Collections.Concurrent命名空间中找到的集合效率,他们让工作线程来更新绑定到视图的有序集合。有我在执行的一些事情,我觉得可以做的更好,所以我欢迎您的意见,正反两方面的,在这篇文章的底部。背景
我想一个线程观察的排序字典,但之前我到了那里,我不得不去通过这些步骤:THREADSAFE观察到收藏可观察到的词典THREADSAFE观察到词典可观察到的排序词典THREADSAFE观察的排序词典THREADSAFE观察的集合
我开始与NET框架的ObservableCollection和它线程,牢记下面的事情。防止被修改,而一个值被读出的集合防止外部事件的处理时间被修改集合 集合是有序的,不是一个队列,所以项目需要插入需要能够手头宽裕,由于集合的性质下令,不会被收集的后续变化影响可列举
我想出了一个基类,使用控制一个封装的ObservableCollection对象的读写访问。NET ReaderWriterLock,并遍历所有事件处理程序和使用DispatcherObject的类型提出任何调度集合更改事件。要创建一个以后的变化,不会受到影响的枚举,我只是创建一个快照的收集,所以不是最完美的解决方案,但足以满足我的需求。
在这里集合时写入的程序:获取读锁定升级到写锁 需要设置的标志,表明一个新的枚举的快照更新封装的ObservableCollection从封装的收集处理事件:降级锁,读锁遍历事件处理程序如果事件处理程序是一个DispatcherObject的调用使用Dispatcher的处理程序,否则在当前线程中调用的处理程序。如果锁仍是一个写锁,降级到一个读锁释放读锁
在这里集合时读取程序:获取读锁定返回值释放读锁
在这里当枚举是要求程序:获取读锁定检查收集快照标志,看看是否需要一个新的快照如果需要新的集合快照:收集快照获取写锁创建一个新的集合快照清除集合快照更新所需的标志发布收集快照的写锁返回从快照枚举
看起来相当简单的,但有一个问题。隐藏在这些程序是一个种族出现一个WPF的ListView类型的控制请求在中间写的枚举,有下列情况:ListView的等待写锁被释放收集编写程序释放写锁,然后等待更新ListView的线程上调用ListView中获取已更新的集合枚举并显示新的列表收集编写程序火灾项目添加事件获取项目的ListView添加的事件,并增加了一个项目,已经显示视图,因此错误地在视图中创建一个重复
这可以发生在视图初始化,因为枚举是没有要求,直到ListView是第一可见,在这一点名单可能正在更新中。为了解决这个问题,你需要一个单独为每个处理程序快照,但并非所有的处理程序,将有一个调度线程,你可以映射到快照。我研究了一下一些线程级别的存储类型的方法,但映射分派线程自首的快照版本。所以现在的程序集合时写入额外的步骤,看起来像这样: 获取读锁定升级到写锁复制当前集合快照句柄需要设置的标志,表明一个新的枚举的快照更新封装的ObservableCollection从封装的ObservableCollection处理事件:建立分派线程尚未更新列表降级锁,读锁遍历事件处理程序如果事件处理程序是一个DispatcherObject的调用使用Dispatcher的处理程序,否则在当前线程中调用的处理程序。从尚未更新的线程列表中删除调度线程如果锁仍是一个写锁,降级到一个读锁 释放读锁
在读方面,如果读线程在尚未更新的主题列表,那么它交给了旧的快照。
解决方案是不完美,而很少能够被绊倒时的观点是初始化或响应复位和错过2更新的快照。该解决方案,并配合多个线程的调度员虽然。如果你只有1个调度线程,那么你可以做调度线程集​​合的更新,这将是很容易实施开始,我提供的代码。写程序代码

/// <summary>

/// Calls the read function passed in, and if it returns true,

/// then calls the next read function, else calls the write

/// function.

/// </summary>

protected TResult DoBaseReadWrite<TResult>(Func<bool> readFuncTest, 

	Func<TResult> readFunc, Func<TResult> writeFunc) {

  readWriteLock.AcquireReaderLock(Timeout.Infinite);

  try {

    if(readFuncTest()) {

      return readFunc();

    } else {

      lockCookie = readWriteLock.UpgradeToWriterLock(Timeout.Infinite);

      try {

        this.previousBaseSnapshot = this.BaseSnapshot;

        newSnapshotRequired = true;

        TResult returnValue = writeFunc();

        return returnValue;

      } finally {

        if(readWriteLock.IsWriterLockHeld) {

          readWriteLock.DowngradeFromWriterLock(ref lockCookie);

          lockCookie = default(LockCookie);

        }

      }

    }

  } finally {

    readWriteLock.ReleaseReaderLock();

  }

}
{C}
/// <summary>

/// Handles passing on the CollectionChanged event when the base collection

/// fires it.

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e) {



  var otherHandlers = new List<NotifyCollectionChangedEventHandler>();

  var eventHandler = this.CollectionChanged;

  if(eventHandler != null) {



    // Iterate through all the handlers, and group by dispatcher thread



    foreach(NotifyCollectionChangedEventHandler handler 

		in eventHandler.GetInvocationList()) {

      DispatcherObject dispatcherObject = handler.Target as DispatcherObject;

      if(dispatcherObject != null && dispatcherObject.CheckAccess() == false) {

            

        // Get the dispatcher thread id, and add it to the list of threads

        // that need to be handed an enumerable for the previous snapshot.

        // The threads are removed from the list when they're used to invoke

        // the collection changed event.

            

        int threadId = dispatcherObject.Dispatcher.Thread.ManagedThreadId;

        List<NotifyCollectionChangedEventHandler> handlers;

        if(requiresPreviousEnumerator.TryGetValue(threadId, out handlers)) {

          handlers.Add(handler);

        } else {

          handlers = new List<NotifyCollectionChangedEventHandler>();

          handlers.Add(handler);

          requiresPreviousEnumerator.TryAdd(threadId, handlers);

        }

      } else {

        otherHandlers.Add(handler);

      }

    }

  }



  readWriteLock.DowngradeFromWriterLock(ref lockCookie);

  lockCookie = default(LockCookie);



  // We have now transitioned to a read lock so another modification, which 

  // requires a write lock, won't occur until we release the read lock, but

  // at the same time anything can read the collection, including enumerators

  // unless the read thread is in the requiresPreviousEnumerator list, in which

  // case it will be handed the previous snapshot.



  if(eventHandler == null)

    return;



  var handlersByThread = new List<KeyValuePair<int, 

	List<NotifyCollectionChangedEventHandler>>>(requiresPreviousEnumerator);

  foreach(var handlers in handlersByThread) {

    var firstHandler = handlers.Value[0];

    DispatcherObject dispatcherObject = firstHandler.Target as DispatcherObject;

    dispatcherObject.Dispatcher.Invoke(DispatcherPriority.DataBind, (Action)(() => {

      List<NotifyCollectionChangedEventHandler> removedHandlers;



      // remove the current thread id deom the requiresPreviousEnumerator list

      requiresPreviousEnumerator.TryRemove(handlers.Key, out removedHandlers);



      // Iterate through all the dispatchers

      foreach(var handler in handlers.Value) {

        try {

          handler(this, e);

        } catch(Exception) { }

      }

    }));

  }



  // Execute non-dispatcher handlers on current thread

  foreach(var handler in otherHandlers) {

    try {

      handler(this, e);

    } catch(Exception) { }

  }

}
读程序代码
/// <summary>

/// Handles read access from the base collection

/// </summary>

protected TResult DoBaseRead<TResult>(Func<TResult> readFunc) {

  readWriteLock.AcquireReaderLock(Timeout.Infinite);

  try {

    return readFunc();

  } finally {

    readWriteLock.ReleaseReaderLock();

  }

}
取得统计员守则观察到的词典
所以现在我有一个包装获得观察的集合,使其适合线程是不是GUI线程更新的方法。因此,所有我需要做的是包装ObservableDictionary类。NET,但是在当前时间,所以我写我自己。
ObservableDictionary我写的类封装2集合: ,是根据观察到的ObservableDictionary部分的一个键 - 值对的ObservableCollection在基地的ObservableCollection一个字典,地图的关键指数
然而,这并不让我整齐插入到的ObservableCollection不更新一大堆的字典条目的项目,所以我所做的是在连接的顺序是一样的,而不是指数词典的存储链接列表节点该命令的ObservableCollection。链接列表节点都有一个递归递减,并转移他们取出或插入节点时点的指数递增的方法。下面的代码显示了它们是如何工作的:
/// <summary>

/// This function effectively removes this node from the linked list,

/// and decrements the position index of all the nodes that follow it.

/// It removes the node by changing the nodes that come before and

/// after it to point to each other, thus bypassing this node.

/// </summary>

public void Remove() {

  if (this.Previous != null) {

    this.Previous.Next = this.Next;

  }

  if (this.Next != null) {

    this.Next.Previous = this.Previous;

  }

  DecrementForward();

}



/// <summary>

/// This recursive function decrements the position index of all the nodes

/// in front of this node. Used for when a node is removed from a list.

/// </summary>

private void DecrementForward() {

  if (this.Next != null) {

    this.Next.Index--;

    this.Next.DecrementForward();

  }

}



/// <summary>

/// This recursive function decrements the position index of all the nodes

/// in front of this node. Used for when a node is inserted into a list.

/// </summary>

private void IncrementForward() {

  if (this.Next != null) {

    this.Next.Index++;

    this.Next.IncrementForward();

  }

}

有一次,我ObservableDictionary类,线程版本只是包装与存取一大堆ObservableDictionary的问题,除了Keys和Values​​属性。读出的词典如果有必要,这是更新时的键或值属性是只读的快照键和值。观察到的排序词典
此时,ObservableDictionary排序的版本只是简单的指定索引插入一个新的项目添加一个项目时的问题。对于这一点,我用了一个二进制排序算法:
/// <summary>

/// Gets the position for a key to be inserted such that the sort order is maintained.

/// </summary>

/// <param name="key"></param>

/// <returns></returns>

public int GetInsertIndex(int count, TKey key, Func<int, TKey> indexToKey) {

  return BinarySearchForIndex(0, count - 1, key, indexToKey);

}



/// <summary>

/// Searches for the index of the insertion point for the key passed in such that

/// the sort order is maintained. Implemented as a non-recursive method.

/// </summary>

/// <param name="low"></param>

/// <param name="high"></param>

/// <param name="key"></param>

/// <returns></returns>

private int BinarySearchForIndex

(int low, int high, TKey key, Func<int, TKey> indexToKey) {

  while (high >= low) {



    // Calculate the mid point and determine if the key passed in



    // should be inserted at this point, below it, or above it.

    int mid = low + ((high - low) >> 1);

    int result = this.Compare(indexToKey(mid), key);



    // Return the current position, or change the search bounds

    // to be above or below the mid point depending on the result.

    if (result == 0)

      return mid;

    else if (result < 0)

      low = mid + 1;

    else

      high = mid - 1;

  }

  return low;

}

的ObservableDictionary,它只是一个从我写的线程ConcurrentObservableBase类的访问器包装ObservableSortedDictionary的问题。使用代码
Swordfish.NET.General项目附带的源代码,我提供了以下的命名空间Swordfish.NET.Collections下的集合类:ObservableDictionaryObservableSortedDictionaryConcurrentObservableCollectionConcurrentObservableDictionaryConcurrentObservableSortedDictionary
附带的源代码还包含一个WPF应用程序,您可以使用一些轻上述集合类的交互式测试。兴趣点
。NET 4.0中,微软已经实施了一些无序的并发集合,包括一个
约翰席梦思代码项目于2010年5月的背部和一个实现。历史首次发表于2011年6月8日修正了一些奥利海斯2011年9月12日发现的问题

回答

评论会员:MooseDePaques 时间:2012/02/03
你知道,如果它可以在Asp.Net应用程序中使用,因为分派System.Windows.Threading命名空间内
评论会员:约翰Stewien 时间:2012/02/03
我已不使用ASP.NET,因为我是一个桌面应用程序的程序员,但我看不到任何理由它不会工作
评论会员:。boulolou 时间:2012/02/03
我有调理,我想在网格中显示的数据和计算排序字典。如何(如果可能的话吗?),我可以链接排序的字典到一个WPF UserControl的数据网格吗?
感谢
Boulolou
评论会员:约翰Stewien 时间:2012/02/03
你不能直接做到这一点,你需要有一个容器类,一格的字典集合映射,可能是你需要编写使用作为我ConcurrentObservableBase基类
评论会员:。boulolou 时间:2012/02/03
感谢您的回应。然而,如果我的排序dictionnary包含我想要显示的数据计算一劳永逸之前,我想绘制并显示在DataGrid,我应该仍然可以使用您的ConcurrentObservableBase,才有可能转移我的数据,在两列的DataTable它链接到一个WPF DataGrid的
评论会员:?约翰Stewien 时间:2012/02/03
如果数据没有改变,那么你就没有需要观察的集合,你可以只复制数据,静态数据结构

如果它的仅有2列,1密钥,值1,然后你可以使用,在排序字典也价值的关键对清单双打
评论会员:。OllyHayes 时间:2012/02/03
只要一直围绕有点打,除非我弄错了,它并没有看起来像字典属性更新您的键和值。你contructing在字典的构造,但是当调用UpdateSnapshot()基快照得到重建,因此,键和值的集合举行参考是过时了。

你觉得可能删除的成员变量和更换喜欢的东西更重要的属性:

public ICollection<TKey> Keys

{

    get {return new ConcurrentKeyCollection<TKey, TValue>(this);}

}

 

public ICollection<TValue> Values

{

    get {return new ConcurrentValueCollection<TKey, TValue>(this;}

}


评论会员:约翰Stewien 时间:2012/02/03
,这是故意的,是关系到实施的GetEnumerator()也返回快照 枚举器,值,或一个GUI对象的关键,如果你的手,你将潜在的崩溃,因为集合可以由另一个线程修改GUI的枚举。
我想有一个可以不引起异常的情况下修改底层集合的枚举,并有新的对象添加到末尾,但然后会如何排序的版本(这实际上是我的最终目标)
工作
我将投入上面的代码中的注释
评论会员:。OllyHayes 时间:2012/02/03
对不起,我想你误解了我,我明白为什么你使用快照,但我要说的是什么,Keys和Values​​属性唐"T返回最新的快照。

例如,如果你运行下面的代码:


ConcurrentObservableDictionary<string, string> dictionary = new ConcurrentObservableDictionary<string,string>();

 

dictionary.Add("key1", "value1");

dictionary.Add("key2", "value2");

dictionary.Add("key3", "value3");

 

Console.WriteLine("keys collection - {0}", dictionary.Keys.Count);

foreach (string key in dictionary.Keys)

    Console.WriteLine("    keys collection - {0}", key);

 

Console.WriteLine("values collection - {0}", dictionary.Values.Count);

foreach (string value in dictionary.Values)

    Console.WriteLine("    values collection - {0}", value);

 

Console.WriteLine("kvp collection - {0}", dictionary.Count);

foreach (KeyValuePair<string, string> kvp in dictionary)

    Console.WriteLine("    kvp collection - {0}, {1}", kvp.Key, kvp.Value);


我们得到以下输出:


keys collection - 0

values collection - 0

kvp collection - 3

    kvp collection - key1, value1

    kvp collection - key2, value2

    kvp collection - key3, value3


因此,键和值属性仍然使用日期的快照的实例了。如果我们重新创建通过以下更改您ConcurrentObservableDictionary的列表:


//public ICollection<TKey> Keys

//{

//    get

//    {

//        return DoBaseRead(delegate()

//        {

//            return this.keys;

//        });

//    }

//}



//public ICollection<TValue> Values

//{

//    get

//    {

//        return DoBaseRead(delegate()

//        {

//            return this.values;

//        });

//    }

//}

        

public ICollection<TKey> Keys

{

    get {return new ConcurrentKeyCollection<TKey, TValue>(this);}

}

 

public ICollection<TValue> Values

{

    get {return new ConcurrentValueCollection<TKey, TValue>(this);}

}


那么,我们得到以下输出:


keys collection - 3

    keys collection - key1

    keys collection - key2

    keys collection - key3

values collection - 3

    values collection - value1

    values collection - value2

    values collection - value3

kvp collection - 3

    kvp collection - key1, value1

    kvp collection - key2, value2

    kvp collection - key3, value3


评论会员:约翰Stewien 时间:2012/02/03
对了,我看现在的问题。试想想它,我没有使用过这些键和值属性自己,使未经测试的代码{S1}我做的工作的步伐并没有给我很大的呼吸空间,人们靠我来,并得到东西现在的工作! ,但我得到快速反馈,因此通常的错误是拿起快速....如果使用代码
评论会员:约翰Stewien 时间:2012/02/03
我更新的文章,滚入源的变化,并把一张纸条约在历史部分的贡献
评论会员:。OllyHayes 时间:2012/02/03
不知道如果我失去了一些东西在这里,但我如何做有点困惑。我有一个绑定到一个WPF控制的字典,一个工作线程正在更新。如果辅助线程要更新项目,如果在字典中的,并把它添加如果没有,那么我怎么能确定的GUI还没有新增项目之间的检查,如果它的存在,并将其添加?


ConcurrentObservableDictionary<string, SomeClass> _dictionary = new ConcurrentObservableDictionary<string, SomeClass>();

 

public void SetValue(string key, object value)

{

    SomeClass myClass;

    if (_dictionary.TryGetValue(key, out myClass))    //1

        myClass.Value = value;

    else

    {

        myClass = new SomeClass(value);

        _dictionary.Add(key, myClass);                //2

    }

}


因此,如果GUI添加一个对象使用相同的密钥,而工人线程1和2之间,那么我们将得到一个异常。至于我可以告诉大家,有没有办法判断,这是怎么回事发生的。或者是有?

任何帮助的感谢
评论会员:!约翰Stewien 时间:2012/02/03
你说得对,您提供的代码可能会导致一个异常,如果从2个不同的线程更新,和我的代码站在那里的方式是没有办法解决这个问题

我们需要的是像这样的方法:
public bool TryAdd(string key, object value)

的代码,因为它被设计为从1线程更新,并从另一个消耗。你可以有一个NET在你排队要添加或设置的项目,这是从辅助线程消耗。
V4 BlockingCollection
我会做什么,看看代码今晚,和工作创建TryAdd我上述建议的方法,并答复后的代码,再后来,当我多一点时间,我会更新的文章。
评论会员:约翰Stewien 时间:2012/02/03
我添加了下面的方法,让你做一个锁内2读取和写的ConcurrentObservableBase:

/// <summary>

/// Calls the read function passed in, and if it returns true,

/// then calls the next read function, else calls the write

/// function.

/// </summary>

protected TResult DoBaseReadWrite<TResult>(Func<bool> readFuncTest, Func<TResult> readFunc, Func<TResult> writeFunc) {

  readWriteLock.AcquireReaderLock(Timeout.Infinite);

  WaitForDispatcherThreadsToIdle();

  try {

    if(readFuncTest()) {

      return readFunc();

    } else {

      lockCookie = readWriteLock.UpgradeToWriterLock(Timeout.Infinite);

      try {

        TResult returnValue = writeFunc();

        newSnapshotRequired = true;

        return returnValue;

      } finally {

        if(readWriteLock.IsWriterLockHeld) {

          readWriteLock.DowngradeFromWriterLock(ref lockCookie);

          lockCookie = default(LockCookie);

        }

      }

    }

  } finally {

    readWriteLock.ReleaseReaderLock();

  }

}

然后在ConcurrentObservableDictionary我已经添加了2种方法,既利用基上面的方法:

/// <summary>

/// Tries adding a new key value pair.

/// </summary>

/// <param name="key">

/// The object to use as the key of the element to add.

/// </param>

/// <param name="value">

/// The object to use as the value of the element to add.

/// </param>

/// <returns>

/// True if successful or false if the key already exists.

/// </returns>

public bool TryAdd(TKey key, TValue value) {

  return DoBaseReadWrite(() => {

    return keyToIndex.ContainsKey(key);

  }, () => {

    return false;

  }, () => {

    BaseAdd(key, value);

    return true;

  });

}

 

/// <summary>

/// Retrives a value for the key passed in if it exists, else

/// adds the new value passed in.

/// </summary>

/// <param name="key">

/// The object to use as the key of the element to retrieve or add.

/// </param>

/// <param name="value">

/// The object to add if it doesn't already exist

/// </param>

/// <returns></returns>

public TValue RetrieveOrAdd(TKey key, Lazy<TValue> value) {

  return DoBaseReadWrite(() => {

    return keyToIndex.ContainsKey(key);

  }, () => {

    int index = keyToIndex[key].Index;

    return BaseCollection[index].Value;

  }, () => {

    BaseAdd(key, value.Value);

    return value.Value;

  });

}

在您的情况下,你可以使用这样的Ret​​rieveOrAdd的方法:


var _dictionary = new ConcurrentObservableDictionary<string, SomeClass>();

 

public void SetValue(string key, object value)

{

  SomeClass myClass = _dictionary.RetrieveOrAdd(key, new Lazy<SomeClass>(() => new SomeClass(value));

  myClass.Value = value;

}

评论会员:OllyHayes 时间:2012/02/03
这是出色的,感谢您的快速答复

自写我的第一个问题,其实我也注意到System.Collections.Concurrent.ConcurrentDictionary有一个类似GetOrAdd的方法,以及AddOrUpdate方法,也许值得推行,以及?事情是这样的我猜:


public TValue UpdateOrAdd(TKey key, TValue addValue, Func<TKey, TValue, TValue> updateValueFactory)

{

    return DoBaseWrite<TValue>(() =>

        {

            if (keyToIndex.ContainsKey(key))

            {

                int index = keyToIndex[key].Index;

                TValue newValue = updateValueFactory(key, BaseCollection[index].Value);

                BaseCollection[index] = new KeyValuePair<TKey, TValue>(key, newValue);

                return newValue;

            }

            else

            {

                BaseAdd(key, addValue);

                return addValue;

            }

        });

}


此外,我注意到你在基类中使用ReaderWriterLock,但MSDN文档中说,所有新开发的使用ReaderWriterLockSlim。只是想知道是否有一个具体的理由,你不这样做呢?

感谢您的帮助
评论会员:约翰Stewien 时间:2012/02/03
我知道的ReaderWriterLockSlim,我看着它与ReaderWriterLock卡住的原因,现在躲避我。 ReaderWriterLockSlim允许一些不同的递归模式,这可能会允许一个更强大的执行,因为目前有一种方法(我已经看到了最近的GUI仍然可以得到的第一个项目复制到最终)

别的东西放在我的TODO列表。此外,我应该去通过ConcurrentDictionary和实施的所有方法从那个类。

顺便说一句感谢对我实施的反馈。我开始怀疑是否有人已经实地测试,
评论会员:约翰Stewien 时间:2012/02/03
我设法拿出一个经常复制问题的测试,从之前事件触发更新的集合的GUI更新,从而不同步。我也想出一个更可靠的方法,试图避免这种情况的 - 至少比我的WaitForDispatcherThreadsT​​oIdle()强大的黑客

所有的变化都在ConcurrentObservableBase.cs,自然,我会尽快更新的文章。

我添加了以下字段:
using System.Collections.Concurrent;

...

 

/// <summary>

/// The previous snapshot for when we don't want to expose the

/// latest change to the calling thread.

/// </summary>

private ImmutableCollectionBase<T> previousBaseSnapshot;

/// <summary>

/// A list of threads that require the previous snapshot because

/// they haven't been passed the collection changed event yet.

/// </summary>

private ConcurrentDictionary<int, List<NotifyCollectionChangedEventHandler>> requiresPreviousEnumerator;
更新的构造:
/// <summary>

/// Default Constructor

/// </summary>

protected ConcurrentObservableBase(){

  this.baseCollection = new ObservableCollection<T>();

  this.baseSnapshot = new ImmutableCollection<T>();

  this.baseCollection.CollectionChanged += this.BaseCollectionChanged;

 

  this.previousBaseSnapshot = this.baseSnapshot;

  this.requiresPreviousEnumerator = new ConcurrentDictionary<int, List<NotifyCollectionChangedEventHandler>>();

}
增加了一个在BaseSnaphot属性检查:
/// <summary>

/// Gets an immutable snapshot of the collection

/// </summary>

public ImmutableCollectionBase<T> BaseSnapshot {

  get {

    return this.DoBaseRead(() => {

      if(this.requiresPreviousEnumerator.ContainsKey(Thread.CurrentThread.ManagedThreadId)) {

        return this.previousBaseSnapshot;

      } else {

        UpdateSnapshot();

        return this.baseSnapshot;

      }

    });

  }

}
主要变化是在OnCollectionChanged()现在看起来像这样:
/// <summary>

/// Handles passing on the CollectionChanged event when the base collection

/// fires it.

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e) {

 

  var otherHandlers = new List<NotifyCollectionChangedEventHandler>();

  var eventHandler = this.CollectionChanged;

  if(eventHandler != null) {

 

    // Iterate through all the handlers, and group by dispatcher thread



    foreach(NotifyCollectionChangedEventHandler handler in eventHandler.GetInvocationList()) {

      DispatcherObject dispatcherObject = handler.Target as DispatcherObject;

      if(dispatcherObject != null && dispatcherObject.CheckAccess() == false) {

 

        // Get the dispatcher thread id, and add it to the list of threads

        // that need to be handed an enumerable for the previous snapshot.

        // The threads are removed from the list when they're used to invoke

        // the collection changed event.



        int threadId = dispatcherObject.Dispatcher.Thread.ManagedThreadId;

        List<NotifyCollectionChangedEventHandler> handlers;

        if(requiresPreviousEnumerator.TryGetValue(threadId, out handlers)) {

          handlers.Add(handler);

        } else {

          handlers = new List<NotifyCollectionChangedEventHandler>();

          handlers.Add(handler);

          requiresPreviousEnumerator.TryAdd(threadId, handlers);

        }

      } else {

        otherHandlers.Add(handler);

      }

    }

  }

 

  readWriteLock.DowngradeFromWriterLock(ref lockCookie);

  lockCookie = default(LockCookie);

 

  // We have now transitioned to a read lock so another modification, which

  // requires a write lock, won't occur until we release the read lock, but

  // at the same time anything can read the collection, including enumerators

  // unless the read thread is in the requiresPreviousEnumerator list, in which

  // case it will be handed the previous snapshot.



  if(eventHandler == null)

    return;

 

  var handlersByThread = new List<KeyValuePair<int, List<NotifyCollectionChangedEventHandler>>>(requiresPreviousEnumerator);

  foreach(var handlers in handlersByThread) {

    var firstHandler = handlers.Value[0];

    DispatcherObject dispatcherObject = firstHandler.Target as DispatcherObject;

    dispatcherObject.Dispatcher.Invoke(DispatcherPriority.DataBind, (Action)(() => {

      List<NotifyCollectionChangedEventHandler> removedHandlers;

 

      // remove the current thread id deom the requiresPreviousEnumerator list

      requiresPreviousEnumerator.TryRemove(handlers.Key, out removedHandlers);

 

      // Iterate through all the dispatchers

      foreach(var handler in handlers.Value) {

        try {

          handler(this, e);

        } catch(Exception) { }

      }

    }));

  }

 

  // Execute non-dispatcher handlers on current thread

  foreach(var handler in otherHandlers) {

    try {

      handler(this, e);

    } catch(Exception) { }

  }

}
其他变化在2 write方法:
/// <summary>

/// Calls the read function passed in, and if it returns true,

/// then calls the next read function, else calls the write

/// function.

/// </summary>

protected TResult DoBaseReadWrite<TResult>(Func<bool> readFuncTest, Func<TResult> readFunc, Func<TResult> writeFunc) {

  readWriteLock.AcquireReaderLock(Timeout.Infinite);

  //WaitForDispatcherThreadsToIdle();

  try {

    if(readFuncTest()) {

      return readFunc();

    } else {

      lockCookie = readWriteLock.UpgradeToWriterLock(Timeout.Infinite);

      try {

        this.previousBaseSnapshot = this.BaseSnapshot;

        newSnapshotRequired = true;

        TResult returnValue = writeFunc();

        return returnValue;

      } finally {

        if(readWriteLock.IsWriterLockHeld) {

          readWriteLock.DowngradeFromWriterLock(ref lockCookie);

          lockCookie = default(LockCookie);

        }

      }

    }

  } finally {

    readWriteLock.ReleaseReaderLock();

  }

}
/// <summary>

/// Handles write access to the base collection when a return value is required

/// </summary>

/// <typeparam name="TResult"></typeparam>

/// <param name="writeFunc"></param>

/// <returns></returns>

protected TResult DoBaseWrite<TResult>(Func<TResult> writeFunc) {

  readWriteLock.AcquireReaderLock(Timeout.Infinite);

  //WaitForDispatcherThreadsToIdle();

  try {

    lockCookie = readWriteLock.UpgradeToWriterLock(Timeout.Infinite);

    this.previousBaseSnapshot = this.BaseSnapshot;

    newSnapshotRequired = true;

    return writeFunc();

  } finally {

    if (readWriteLock.IsWriterLockHeld) {

      readWriteLock.DowngradeFromWriterLock(ref lockCookie);

      lockCookie = default(LockCookie);

    }

    readWriteLock.ReleaseReaderLock();

  }

}

评论会员:约翰Stewien 时间:2012/02/03
我看看GetOrAdd(),和AddOrUpdate()的行为是,并已决定不把这些,因为它可能会导致编码从我的下线的问题工作,所以最终会回来咬我。

{A3}

AddOrUpdate()和GetOrAdd()方法是完全线程安全的,你可以依赖,但他们不是原子。重要的是要注意使用委托执行的方法,这些代表外锁。
这样做是故意让用户委托(其中ConcurrentDictionary没有过程控制),并不需要太长时间,并锁定其他线程。 &# 160;
这不一定是一个问题,本身,但它是你必须考虑在您的设计。主要要考虑的是您的代理可能会被调用,生成一个项目,但该项目可能不会返回一个!