【问题标题】:Thread-Safe collection with no order and no duplicates无顺序无重复的线程安全集合
【发布时间】:2012-09-17 02:56:56
【问题描述】:

我需要一个线程安全的集合来保存不重复的项目。 ConcurrentBag<T> 允许非唯一项,HashSet<T> 不是线程安全的。 .NET Framework 4.5 中有这样的集合吗?

【问题讨论】:

    标签: c# .net collections concurrency thread-safety


    【解决方案1】:

    我建议您使用 ConcurrentDictionary 并为每个条目使用一个虚拟值。就效率而言(具有所有这些虚拟值)很烦人,但我怀疑在大多数应用程序中这将是微不足道的。

    您可能希望将其包装在您自己的 ConcurrentSet 实现中,这足以满足您的目的,因此您不需要在大部分代码中看到抽象漏洞。

    【讨论】:

    • “虚拟值”的类型有区别吗?使用 ConcurrentDictionary<MyType, object> 并将值设置为 null 就足够了吗?
    • @ŞafakGür:你可以这样做,或者使用值为 0 的 int。由于内存对齐,使用小于 int 的值可能毫无意义。
    • @ŞafakGür 我看到的两个最常见的事情是对所有值都使用 null,或者(对于引用类型)总是将每对的键和值都设为同一个实例。
    • @Servy:谢谢,为键和值设置相同的引用是有意义的,但我按照 Jon 的建议将 ConcurrentDictionary 包装为 ConcurretSet,我可以将它用于引用类型和值类型。我想我会选择 null。
    • @ŞafakGür:您使用的 key 类型与您使用的 value 类型无关。所以对于ConcurrentSet<T>,你可以创建一个ConcurrentDictionary<T, int>
    【解决方案2】:

    这是我刚刚编写的用于执行此 ConcurrentSet 构造的一些代码:

    public class ConcurrentSet<T> : IEnumerable<T>, ISet<T>, ICollection<T>
    {
        private readonly ConcurrentDictionary<T, byte> _dictionary = new ConcurrentDictionary<T, byte>();
    
        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
        /// </returns>
        public IEnumerator<T> GetEnumerator()
        {
            return _dictionary.Keys.GetEnumerator();
        }
    
        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
        /// </returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    
        /// <summary>
        /// Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1"/>.
        /// </summary>
        /// <returns>
        /// true if <paramref name="item"/> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false. This method also returns false if <paramref name="item"/> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1"/>.
        /// </returns>
        /// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</exception>
        public bool Remove(T item)
        {
            return TryRemove(item);
        }
    
        /// <summary>
        /// Gets the number of elements in the set.
        /// </summary>
        public int Count
        {
            get { return _dictionary.Count; }
        }
    
        /// <summary>
        /// Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.
        /// </summary>
        /// <returns>
        /// true if the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only; otherwise, false.
        /// </returns>
        public bool IsReadOnly { get { return false; } }
    
        /// <summary>
        /// Gets a value that indicates if the set is empty.
        /// </summary>
        public bool IsEmpty
        {
            get { return _dictionary.IsEmpty; }
        }
    
        public ICollection<T> Values
        {
            get { return _dictionary.Keys; }
        }
    
        /// <summary>
        /// Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1"/>.
        /// </summary>
        /// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</exception>
        void ICollection<T>.Add(T item)
        {
            if(!Add(item))
                throw new ArgumentException("Item already exists in set.");
        }
    
        /// <summary>
        /// Modifies the current set so that it contains all elements that are present in both the current set and in the specified collection.
        /// </summary>
        /// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
        public void UnionWith(IEnumerable<T> other)
        {
            foreach (var item in other)
                TryAdd(item);
        }
    
        /// <summary>
        /// Modifies the current set so that it contains only elements that are also in a specified collection.
        /// </summary>
        /// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
        public void IntersectWith(IEnumerable<T> other)
        {
            var enumerable = other as IList<T> ?? other.ToArray();
            foreach (var item in this)
            {
                if (!enumerable.Contains(item))
                    TryRemove(item);
            }
        }
    
        /// <summary>
        /// Removes all elements in the specified collection from the current set.
        /// </summary>
        /// <param name="other">The collection of items to remove from the set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
        public void ExceptWith(IEnumerable<T> other)
        {
            foreach (var item in other)
                TryRemove(item);
        }
    
        /// <summary>
        /// Modifies the current set so that it contains only elements that are present either in the current set or in the specified collection, but not both. 
        /// </summary>
        /// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
        public void SymmetricExceptWith(IEnumerable<T> other)
        {
            throw new NotImplementedException();
        }
    
        /// <summary>
        /// Determines whether a set is a subset of a specified collection.
        /// </summary>
        /// <returns>
        /// true if the current set is a subset of <paramref name="other"/>; otherwise, false.
        /// </returns>
        /// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
        public bool IsSubsetOf(IEnumerable<T> other)
        {
            var enumerable = other as IList<T> ?? other.ToArray();
            return this.AsParallel().All(enumerable.Contains);
        }
    
        /// <summary>
        /// Determines whether the current set is a superset of a specified collection.
        /// </summary>
        /// <returns>
        /// true if the current set is a superset of <paramref name="other"/>; otherwise, false.
        /// </returns>
        /// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
        public bool IsSupersetOf(IEnumerable<T> other)
        {
            return other.AsParallel().All(Contains);
        }
    
        /// <summary>
        /// Determines whether the current set is a correct superset of a specified collection.
        /// </summary>
        /// <returns>
        /// true if the <see cref="T:System.Collections.Generic.ISet`1"/> object is a correct superset of <paramref name="other"/>; otherwise, false.
        /// </returns>
        /// <param name="other">The collection to compare to the current set. </param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
        public bool IsProperSupersetOf(IEnumerable<T> other)
        {
            var enumerable = other as IList<T> ?? other.ToArray();
            return this.Count != enumerable.Count && IsSupersetOf(enumerable);
        }
    
        /// <summary>
        /// Determines whether the current set is a property (strict) subset of a specified collection.
        /// </summary>
        /// <returns>
        /// true if the current set is a correct subset of <paramref name="other"/>; otherwise, false.
        /// </returns>
        /// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
        public bool IsProperSubsetOf(IEnumerable<T> other)
        {
            var enumerable = other as IList<T> ?? other.ToArray();
            return Count != enumerable.Count && IsSubsetOf(enumerable);
        }
    
        /// <summary>
        /// Determines whether the current set overlaps with the specified collection.
        /// </summary>
        /// <returns>
        /// true if the current set and <paramref name="other"/> share at least one common element; otherwise, false.
        /// </returns>
        /// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
        public bool Overlaps(IEnumerable<T> other)
        {
            return other.AsParallel().Any(Contains);
        }
    
        /// <summary>
        /// Determines whether the current set and the specified collection contain the same elements.
        /// </summary>
        /// <returns>
        /// true if the current set is equal to <paramref name="other"/>; otherwise, false.
        /// </returns>
        /// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
        public bool SetEquals(IEnumerable<T> other)
        {
            var enumerable = other as IList<T> ?? other.ToArray();
            return Count == enumerable.Count && enumerable.AsParallel().All(Contains);
        }
    
        /// <summary>
        /// Adds an element to the current set and returns a value to indicate if the element was successfully added. 
        /// </summary>
        /// <returns>
        /// true if the element is added to the set; false if the element is already in the set.
        /// </returns>
        /// <param name="item">The element to add to the set.</param>
        public bool Add(T item)
        {
            return TryAdd(item);
        }
    
        public void Clear()
        {
            _dictionary.Clear();
        }
    
        public bool Contains(T item)
        {
            return _dictionary.ContainsKey(item);
        }
    
        /// <summary>
        /// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1"/> to an <see cref="T:System.Array"/>, starting at a particular <see cref="T:System.Array"/> index.
        /// </summary>
        /// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied from <see cref="T:System.Collections.Generic.ICollection`1"/>. The <see cref="T:System.Array"/> must have zero-based indexing.</param><param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param><exception cref="T:System.ArgumentNullException"><paramref name="array"/> is null.</exception><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="arrayIndex"/> is less than 0.</exception><exception cref="T:System.ArgumentException"><paramref name="array"/> is multidimensional.-or-The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1"/> is greater than the available space from <paramref name="arrayIndex"/> to the end of the destination <paramref name="array"/>.-or-Type <paramref name="T"/> cannot be cast automatically to the type of the destination <paramref name="array"/>.</exception>
        public void CopyTo(T[] array, int arrayIndex)
        {
            Values.CopyTo(array, arrayIndex);
        }
    
        public T[] ToArray()
        {
            return _dictionary.Keys.ToArray();
        }
    
        public bool TryAdd(T item)
        {
            return _dictionary.TryAdd(item, default(byte));
        }
    
        public bool TryRemove(T item)
        {
            byte donotcare;
            return _dictionary.TryRemove(item, out donotcare);
        }
    }
    

    【讨论】:

    • +1 用于实际代码,但我不能不接受 Jon 的回答,因为我所问的只是 .net 是否有替代方案,他为我提供了准确的建议。不过,您的实现比我的要好。谢谢,我会用这个。我只是希望我能给予更多的支持。
    • 没关系。老实说,因为我只是在寻找自己的解决方案,所以我没有查看您提出问题的日期。我以为我在回答一个古老的问题,而不是几天前的问题。 :-)
    • 我最近又看了一遍,我认为SymmetricExceptWith 可以这样实现:foreach (var item in other) if (!Remove(item)) Add(item); 我不知道什么时候会使用它。
    猜你喜欢
    • 2011-12-15
    • 1970-01-01
    • 2013-12-10
    • 1970-01-01
    • 2011-02-28
    • 1970-01-01
    • 2014-09-06
    • 1970-01-01
    • 2011-05-29
    相关资源
    最近更新 更多