{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个调度线程,那么你可以做调度线程集合的更新,这将是很容易实施开始,我提供的代码。写程序代码
{C}/// <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();
}
}
读程序代码/// <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日发现的问题