diff --git a/src/Umbraco.Core/Collections/ConcurrentHashSet.cs b/src/Umbraco.Core/Collections/ConcurrentHashSet.cs index 0424c4dae1..c4dba51acd 100644 --- a/src/Umbraco.Core/Collections/ConcurrentHashSet.cs +++ b/src/Umbraco.Core/Collections/ConcurrentHashSet.cs @@ -1,8 +1,8 @@ using System; using System.Collections; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Threading; namespace Umbraco.Core.Collections { @@ -14,10 +14,9 @@ namespace Umbraco.Core.Collections [Serializable] public class ConcurrentHashSet : ICollection { - // Internally we just use a ConcurrentDictionary with the same value for the Value (since we don't actually care about the value) - private static readonly byte _emptyValue = 0x0; - private readonly ConcurrentDictionary _innerSet = new ConcurrentDictionary(); - + private readonly HashSet _innerSet = new HashSet(); + private readonly ReaderWriterLockSlim _instanceLocker = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); + /// /// Returns an enumerator that iterates through the collection. /// @@ -27,7 +26,7 @@ namespace Umbraco.Core.Collections /// 1 public IEnumerator GetEnumerator() { - return _innerSet.Keys.GetEnumerator(); + return GetThreadSafeClone().GetEnumerator(); } /// @@ -51,7 +50,16 @@ namespace Umbraco.Core.Collections /// The object to remove from the .The is read-only. public bool Remove(T item) { - return _innerSet.TryRemove(item, out _); + try + { + _instanceLocker.EnterWriteLock(); + return _innerSet.Remove(item); + } + finally + { + if (_instanceLocker.IsWriteLockHeld) + _instanceLocker.ExitWriteLock(); + } } @@ -66,7 +74,17 @@ namespace Umbraco.Core.Collections { get { - return _innerSet.Count; + try + { + _instanceLocker.EnterReadLock(); + return _innerSet.Count; + } + finally + { + if (_instanceLocker.IsReadLockHeld) + _instanceLocker.ExitReadLock(); + } + } } @@ -81,10 +99,19 @@ namespace Umbraco.Core.Collections /// /// Adds an item to the . /// - /// The object to add to the . + /// The object to add to the .The is read-only. public void Add(T item) { - _innerSet.TryAdd(item, _emptyValue); + try + { + _instanceLocker.EnterWriteLock(); + _innerSet.Add(item); + } + finally + { + if (_instanceLocker.IsWriteLockHeld) + _instanceLocker.ExitWriteLock(); + } } /// @@ -94,7 +121,21 @@ namespace Umbraco.Core.Collections /// public bool TryAdd(T item) { - return _innerSet.TryAdd(item, _emptyValue); + if (Contains(item)) return false; + try + { + _instanceLocker.EnterWriteLock(); + + //double check + if (_innerSet.Contains(item)) return false; + _innerSet.Add(item); + return true; + } + finally + { + if (_instanceLocker.IsWriteLockHeld) + _instanceLocker.ExitWriteLock(); + } } /// @@ -103,7 +144,16 @@ namespace Umbraco.Core.Collections /// The is read-only. public void Clear() { - _innerSet.Clear(); + try + { + _instanceLocker.EnterWriteLock(); + _innerSet.Clear(); + } + finally + { + if (_instanceLocker.IsWriteLockHeld) + _instanceLocker.ExitWriteLock(); + } } /// @@ -115,7 +165,16 @@ namespace Umbraco.Core.Collections /// The object to locate in the . public bool Contains(T item) { - return _innerSet.ContainsKey(item); + try + { + _instanceLocker.EnterReadLock(); + return _innerSet.Contains(item); + } + finally + { + if (_instanceLocker.IsReadLockHeld) + _instanceLocker.ExitReadLock(); + } } /// @@ -124,8 +183,24 @@ namespace Umbraco.Core.Collections /// The one-dimensional that is the destination of the elements copied from the . The array must have zero-based indexing.The zero-based index in at which copying begins. is a null reference (Nothing in Visual Basic). is less than zero. is equal to or greater than the length of the -or- The number of elements in the source is greater than the available space from to the end of the destination . public void CopyTo(T[] array, int index) { - var keys = _innerSet.Keys; - keys.CopyTo(array, index); + var clone = GetThreadSafeClone(); + clone.CopyTo(array, index); + } + + private HashSet GetThreadSafeClone() + { + HashSet clone = null; + try + { + _instanceLocker.EnterReadLock(); + clone = new HashSet(_innerSet, _innerSet.Comparer); + } + finally + { + if (_instanceLocker.IsReadLockHeld) + _instanceLocker.ExitReadLock(); + } + return clone; } /// @@ -134,8 +209,8 @@ namespace Umbraco.Core.Collections /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. The zero-based index in at which copying begins. is null. is less than zero. is multidimensional.-or- The number of elements in the source is greater than the available space from to the end of the destination . The type of the source cannot be cast automatically to the type of the destination . 2 public void CopyTo(Array array, int index) { - var keys = _innerSet.Keys; - Array.Copy(keys.ToArray(), 0, array, index, keys.Count); + var clone = GetThreadSafeClone(); + Array.Copy(clone.ToArray(), 0, array, index, clone.Count); } } } diff --git a/src/umbraco.sln.DotSettings b/src/umbraco.sln.DotSettings index 3d42d188af..bd8ca8b974 100644 --- a/src/umbraco.sln.DotSettings +++ b/src/umbraco.sln.DotSettings @@ -1,4 +1,5 @@  + True <data><IncludeFilters /><ExcludeFilters /></data> <data /> Disposable construction