mirror of
https://github.com/ldqk/Masuit.Tools.git
synced 2026-10-27 11:16:34 +08:00
- `ConcurrentHashQueue<T>`:线程安全的唯一元素队列,支持基本操作、批量操作、自定义比较器及原子操作。 - `HashQueue<T>`:非线程安全的唯一元素队列,结合 `Queue` 和 `HashSet` 特性,支持基本操作和容量优化。 - 为上述队列类添加扩展方法(如 `EnqueueWithLimit`、`DequeueAll` 等)。
606 lines
19 KiB
C#
606 lines
19 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading;
|
|
|
|
namespace Masuit.Tools.Systems;
|
|
|
|
/// <summary>
|
|
/// 线程安全的唯一元素队列,结合了ConcurrentQueue和ConcurrentDictionary的特性
|
|
/// </summary>
|
|
/// <typeparam name="T">队列中元素的类型</typeparam>
|
|
[DebuggerDisplay("Count = {Count}")]
|
|
[ComVisible(false)]
|
|
public class ConcurrentHashQueue<T> : IProducerConsumerCollection<T>, IReadOnlyCollection<T>, ICollection, ICollection<T>
|
|
{
|
|
private class Node
|
|
{
|
|
public readonly T Value;
|
|
public Node Next;
|
|
|
|
public Node(T value)
|
|
{
|
|
Value = value;
|
|
}
|
|
}
|
|
|
|
private volatile Node _head;
|
|
private volatile Node _tail;
|
|
private readonly ConcurrentHashSet<T> _hashSet;
|
|
private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.SupportsRecursion);
|
|
private int _count;
|
|
|
|
#region 构造函数
|
|
|
|
/// <summary>
|
|
/// 初始化 ConcurrentUniqueQueue 类的新实例
|
|
/// </summary>
|
|
public ConcurrentHashQueue() : this(null, null)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// 初始化 ConcurrentUniqueQueue 类的新实例,使用指定的相等比较器
|
|
/// </summary>
|
|
/// <param name="comparer">用于比较值的 IEqualityComparer</param>
|
|
public ConcurrentHashQueue(IEqualityComparer<T> comparer) : this(null, comparer)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// 初始化 ConcurrentUniqueQueue 类的新实例,包含从指定集合复制的元素
|
|
/// </summary>
|
|
/// <param name="collection">要复制到 ConcurrentUniqueQueue 中的集合</param>
|
|
public ConcurrentHashQueue(IEnumerable<T> collection) : this(collection, null)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// 初始化 ConcurrentUniqueQueue 类的新实例,包含从指定集合复制的元素,并使用指定的相等比较器
|
|
/// </summary>
|
|
/// <param name="collection">要复制到 ConcurrentUniqueQueue 中的集合</param>
|
|
/// <param name="comparer">用于比较值的 IEqualityComparer</param>
|
|
public ConcurrentHashQueue(IEnumerable<T> collection, IEqualityComparer<T> comparer)
|
|
{
|
|
_hashSet = new ConcurrentHashSet<T>(comparer ?? EqualityComparer<T>.Default);
|
|
|
|
// 创建哨兵节点
|
|
_head = _tail = new Node(default(T));
|
|
_count = 0;
|
|
|
|
if (collection != null)
|
|
{
|
|
foreach (var item in collection)
|
|
{
|
|
Enqueue(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion 构造函数
|
|
|
|
#region 核心方法
|
|
|
|
/// <summary>
|
|
/// 将对象添加到 ConcurrentUniqueQueue 的结尾处
|
|
/// </summary>
|
|
/// <param name="item">要添加到 ConcurrentUniqueQueue 的对象</param>
|
|
/// <returns>如果元素成功添加返回 true,如果元素已存在则返回 false</returns>
|
|
public bool Enqueue(T item)
|
|
{
|
|
if (!_hashSet.TryAdd(item))
|
|
return false;
|
|
|
|
// 创建新节点
|
|
var newNode = new Node(item);
|
|
|
|
// 使用原子操作添加节点到链表尾部
|
|
while (true)
|
|
{
|
|
var tail = _tail;
|
|
var next = tail.Next;
|
|
|
|
if (tail == _tail)
|
|
{
|
|
if (next == null)
|
|
{
|
|
// 尝试链接新节点
|
|
if (Interlocked.CompareExchange(ref tail.Next, newNode, null) == null)
|
|
{
|
|
// 尝试移动tail指针
|
|
Interlocked.CompareExchange(ref _tail, newNode, tail);
|
|
Interlocked.Increment(ref _count);
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// 帮助其他线程移动tail指针
|
|
Interlocked.CompareExchange(ref _tail, next, tail);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 尝试移除并返回位于 ConcurrentUniqueQueue 开始处的对象
|
|
/// </summary>
|
|
/// <param name="result">如果操作成功,则返回已移除的对象;否则为 T 的默认值</param>
|
|
/// <returns>如果成功移除并返回了对象,则为 true;否则为 false</returns>
|
|
public bool TryDequeue(out T result)
|
|
{
|
|
while (true)
|
|
{
|
|
var head = _head;
|
|
var tail = _tail;
|
|
var next = head.Next;
|
|
|
|
if (head == _head)
|
|
{
|
|
if (head == tail)
|
|
{
|
|
if (next == null)
|
|
{
|
|
result = default(T);
|
|
return false;
|
|
}
|
|
|
|
// 帮助其他线程移动tail指针
|
|
Interlocked.CompareExchange(ref _tail, next, tail);
|
|
}
|
|
else
|
|
{
|
|
result = next.Value;
|
|
|
|
// 尝试移动head指针
|
|
if (Interlocked.CompareExchange(ref _head, next, head) == head)
|
|
{
|
|
// 从set中移除元素
|
|
_hashSet.Remove(result);
|
|
Interlocked.Decrement(ref _count);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 尝试返回位于 ConcurrentUniqueQueue 开始处的对象但不将其移除
|
|
/// </summary>
|
|
/// <param name="result">如果操作成功,则返回开头的对象;否则为 T 的默认值</param>
|
|
/// <returns>如果成功返回了对象,则为 true;否则为 false</returns>
|
|
public bool TryPeek(out T result)
|
|
{
|
|
while (true)
|
|
{
|
|
var head = _head;
|
|
var next = head.Next;
|
|
|
|
if (next == null)
|
|
{
|
|
result = default(T);
|
|
return false;
|
|
}
|
|
|
|
if (head == _head)
|
|
{
|
|
result = next.Value;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 确定某元素是否在 ConcurrentUniqueQueue 中
|
|
/// </summary>
|
|
/// <param name="item">要在 ConcurrentUniqueQueue 中定位的对象</param>
|
|
/// <returns>如果在 ConcurrentUniqueQueue 中找到 item,则为 true;否则为 false</returns>
|
|
public bool Contains(T item)
|
|
{
|
|
return _hashSet.Contains(item);
|
|
}
|
|
|
|
/// <summary>Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1"></see>.</summary>
|
|
/// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1"></see>.</param>
|
|
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only.</exception>
|
|
public void Add(T item)
|
|
{
|
|
Enqueue(item);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 从 ConcurrentUniqueQueue 中移除所有对象
|
|
/// </summary>
|
|
public void Clear()
|
|
{
|
|
_lock.EnterWriteLock();
|
|
try
|
|
{
|
|
_hashSet.Clear();
|
|
_head = _tail = new Node(default(T));
|
|
_count = 0;
|
|
}
|
|
finally
|
|
{
|
|
if (_lock.IsWriteLockHeld)
|
|
{
|
|
_lock.ExitWriteLock();
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion 核心方法
|
|
|
|
#region 属性和状态
|
|
|
|
/// <summary>Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1"></see>.</summary>
|
|
/// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"></see>.</param>
|
|
/// <returns>true if <paramref name="item">item</paramref> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1"></see>; otherwise, false. This method also returns false if <paramref name="item">item</paramref> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1"></see>.</returns>
|
|
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only.</exception>
|
|
public bool Remove(T item)
|
|
{
|
|
_lock.EnterWriteLock();
|
|
try
|
|
{
|
|
// 双重检查,确保元素仍然存在
|
|
if (!_hashSet.Contains(item))
|
|
return false;
|
|
|
|
// 从HashSet中移除
|
|
_hashSet.Remove(item);
|
|
|
|
// 遍历链表重新构建,跳过要移除的元素
|
|
var newHead = new Node(default(T));
|
|
var newTail = newHead;
|
|
var current = _head.Next;
|
|
var found = false;
|
|
|
|
while (current != null)
|
|
{
|
|
if (!_hashSet.Comparer.Equals(current.Value, item))
|
|
{
|
|
newTail.Next = new Node(current.Value);
|
|
newTail = newTail.Next;
|
|
}
|
|
else
|
|
{
|
|
found = true;
|
|
}
|
|
current = current.Next;
|
|
}
|
|
|
|
if (found)
|
|
{
|
|
_head = newHead;
|
|
_tail = newTail;
|
|
Interlocked.Decrement(ref _count);
|
|
return true;
|
|
}
|
|
|
|
// 如果没有找到,重新添加到HashSet(理论上不应该发生)
|
|
_hashSet.TryAdd(item);
|
|
return false;
|
|
}
|
|
finally
|
|
{
|
|
if (_lock.IsWriteLockHeld)
|
|
{
|
|
_lock.ExitWriteLock();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取 ConcurrentUniqueQueue 中包含的元素数
|
|
/// </summary>
|
|
public int Count => _count;
|
|
|
|
/// <summary>Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only.</summary>
|
|
/// <returns>true if the <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only; otherwise, false.</returns>
|
|
public bool IsReadOnly { get; }
|
|
|
|
/// <summary>
|
|
/// 获取一个值,该值指示 ConcurrentUniqueQueue 是否为空
|
|
/// </summary>
|
|
public bool IsEmpty => _head.Next == null;
|
|
|
|
#endregion 属性和状态
|
|
|
|
#region IProducerConsumerCollection<T> 实现
|
|
|
|
/// <summary>
|
|
/// 将项添加到 IProducerConsumerCollection
|
|
/// </summary>
|
|
/// <param name="item">要添加到 IProducerConsumerCollection 的项</param>
|
|
/// <returns>如果成功添加了项,则为 true;否则为 false</returns>
|
|
bool IProducerConsumerCollection<T>.TryAdd(T item)
|
|
{
|
|
return Enqueue(item);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 尝试从 IProducerConsumerCollection 中移除和返回对象
|
|
/// </summary>
|
|
/// <param name="item">此方法返回时,如果成功移除并返回了对象,则 item 包含所移除的对象</param>
|
|
/// <returns>如果成功移除并返回了对象,则为 true;否则为 false</returns>
|
|
bool IProducerConsumerCollection<T>.TryTake(out T item)
|
|
{
|
|
return TryDequeue(out item);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 将 IProducerConsumerCollection 的元素复制到新数组
|
|
/// </summary>
|
|
/// <returns>包含从 IProducerConsumerCollection 复制的元素的新数组</returns>
|
|
public T[] ToArray()
|
|
{
|
|
var snapshot = new List<T>(Count);
|
|
var current = _head.Next;
|
|
|
|
while (current != null)
|
|
{
|
|
snapshot.Add(current.Value);
|
|
current = current.Next;
|
|
}
|
|
|
|
return snapshot.ToArray();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 将 IProducerConsumerCollection 的元素复制到现有一维数组中
|
|
/// </summary>
|
|
/// <param name="array">一维数组,它是从 IProducerConsumerCollection 复制的元素的目标</param>
|
|
/// <param name="index">array 中从零开始的索引,从此处开始复制</param>
|
|
public void CopyTo(T[] array, int index)
|
|
{
|
|
if (array == null)
|
|
throw new ArgumentNullException(nameof(array));
|
|
if (index < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(index), "索引不能为负数。");
|
|
if (index >= array.Length)
|
|
throw new ArgumentOutOfRangeException(nameof(index), "索引等于或大于数组长度。");
|
|
|
|
var snapshot = ToArray();
|
|
if (snapshot.Length > array.Length - index)
|
|
throw new ArgumentException("目标数组空间不足。");
|
|
|
|
Array.Copy(snapshot, 0, array, index, snapshot.Length);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 返回循环访问 ConcurrentUniqueQueue 的枚举数
|
|
/// </summary>
|
|
/// <returns>ConcurrentUniqueQueue 的枚举数</returns>
|
|
public IEnumerator<T> GetEnumerator()
|
|
{
|
|
var current = _head.Next;
|
|
while (current != null)
|
|
{
|
|
yield return current.Value;
|
|
current = current.Next;
|
|
}
|
|
}
|
|
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
{
|
|
return GetEnumerator();
|
|
}
|
|
|
|
#endregion IProducerConsumerCollection<T> 实现
|
|
|
|
#region ICollection 实现
|
|
|
|
/// <summary>
|
|
/// 从特定的 ICollection 索引处开始,将数组的元素复制到一个数组中
|
|
/// </summary>
|
|
/// <param name="array">一维数组,它是从 ICollection 复制的元素的目标</param>
|
|
/// <param name="index">array 中从零开始的索引,从此处开始复制</param>
|
|
void ICollection.CopyTo(Array array, int index)
|
|
{
|
|
if (array == null)
|
|
throw new ArgumentNullException(nameof(array));
|
|
if (array.Rank != 1)
|
|
throw new ArgumentException("仅支持一维数组。");
|
|
if (index < 0 || index >= array.Length)
|
|
throw new ArgumentOutOfRangeException(nameof(index));
|
|
|
|
var tArray = array as T[];
|
|
if (tArray != null)
|
|
{
|
|
CopyTo(tArray, index);
|
|
return;
|
|
}
|
|
|
|
var elementType = array.GetType().GetElementType();
|
|
if (!elementType.IsAssignableFrom(typeof(T)))
|
|
throw new ArgumentException("数组类型与集合元素类型不兼容。");
|
|
|
|
var objects = array as object[];
|
|
if (objects == null)
|
|
throw new ArgumentException("目标数组类型不受支持。");
|
|
|
|
try
|
|
{
|
|
var snapshot = ToArray();
|
|
Array.Copy(snapshot, 0, objects, index, snapshot.Length);
|
|
}
|
|
catch (ArrayTypeMismatchException)
|
|
{
|
|
throw new ArgumentException("数组类型与集合元素类型不兼容。");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取一个值,该值指示是否同步对 ICollection 的访问
|
|
/// </summary>
|
|
bool ICollection.IsSynchronized => false;
|
|
|
|
/// <summary>
|
|
/// 获取可用于同步对 ICollection 的访问的对象
|
|
/// </summary>
|
|
object ICollection.SyncRoot => _lock;
|
|
|
|
#endregion ICollection 实现
|
|
|
|
#region 批量操作方法
|
|
|
|
/// <summary>
|
|
/// 批量入队多个元素
|
|
/// </summary>
|
|
/// <param name="items">要入队的元素集合</param>
|
|
/// <returns>成功添加的元素数量</returns>
|
|
public int EnqueueRange(IEnumerable<T> items)
|
|
{
|
|
if (items == null)
|
|
throw new ArgumentNullException(nameof(items));
|
|
|
|
var count = 0;
|
|
foreach (var item in items)
|
|
{
|
|
if (Enqueue(item))
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 批量出队多个元素
|
|
/// </summary>
|
|
/// <param name="count">要出队的元素数量</param>
|
|
/// <returns>出队的元素集合</returns>
|
|
public IEnumerable<T> DequeueRange(int count)
|
|
{
|
|
if (count < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(count), "数量不能为负数。");
|
|
|
|
var result = new List<T>(Math.Min(count, Count));
|
|
for (int i = 0; i < count && TryDequeue(out T item); i++)
|
|
{
|
|
result.Add(item);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endregion 批量操作方法
|
|
|
|
#region 原子操作方法
|
|
|
|
/// <summary>
|
|
/// 原子性地入队元素,如果队列已满则返回false
|
|
/// </summary>
|
|
/// <param name="item">要入队的元素</param>
|
|
/// <param name="maxSize">队列的最大大小</param>
|
|
/// <returns>如果成功入队返回true,否则返回false</returns>
|
|
public bool TryEnqueueWithLimit(T item, int maxSize)
|
|
{
|
|
if (maxSize <= 0)
|
|
throw new ArgumentOutOfRangeException(nameof(maxSize), "最大大小必须大于0。");
|
|
|
|
// 先检查是否已达到最大大小
|
|
if (Count >= maxSize && !Contains(item))
|
|
return false;
|
|
|
|
return Enqueue(item);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 原子性地入队元素,如果队列已满则移除最旧的元素
|
|
/// </summary>
|
|
/// <param name="item">要入队的元素</param>
|
|
/// <param name="maxSize">队列的最大大小</param>
|
|
/// <returns>被移除的元素,如果没有移除则返回default(T)</returns>
|
|
public T EnqueueWithEviction(T item, int maxSize)
|
|
{
|
|
if (maxSize <= 0)
|
|
throw new ArgumentOutOfRangeException(nameof(maxSize), "最大大小必须大于0。");
|
|
|
|
T evicted = default(T);
|
|
|
|
// 如果元素已存在,先移除旧的实例
|
|
if (Contains(item))
|
|
{
|
|
RemoveExistingAndEnqueue(item);
|
|
return default(T);
|
|
}
|
|
|
|
// 检查是否需要驱逐
|
|
if (Count >= maxSize)
|
|
{
|
|
TryDequeue(out evicted);
|
|
}
|
|
|
|
Enqueue(item);
|
|
return evicted;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 移除已存在的元素并重新入队(用于更新元素位置)
|
|
/// </summary>
|
|
/// <param name="item">要更新的元素</param>
|
|
/// <returns>如果成功更新返回true,否则返回false</returns>
|
|
private bool RemoveExistingAndEnqueue(T item)
|
|
{
|
|
// 这个操作比较复杂,需要锁定整个队列
|
|
_lock.EnterWriteLock();
|
|
try
|
|
{
|
|
if (!_hashSet.Contains(item))
|
|
return false;
|
|
|
|
// 从字典中暂时移除
|
|
_hashSet.Remove(item);
|
|
|
|
// 重新构建链表,跳过要更新的元素
|
|
var newHead = new Node(default(T));
|
|
var newTail = newHead;
|
|
var current = _head.Next;
|
|
var found = false;
|
|
|
|
while (current != null)
|
|
{
|
|
if (!_hashSet.Comparer.Equals(current.Value, item))
|
|
{
|
|
newTail.Next = new Node(current.Value);
|
|
newTail = newTail.Next;
|
|
_hashSet.TryAdd(current.Value);
|
|
}
|
|
else
|
|
{
|
|
found = true;
|
|
}
|
|
current = current.Next;
|
|
}
|
|
|
|
if (found)
|
|
{
|
|
// 添加新元素到尾部
|
|
newTail.Next = new Node(item);
|
|
newTail = newTail.Next;
|
|
_hashSet.TryAdd(item);
|
|
|
|
_head = newHead;
|
|
_tail = newTail;
|
|
_count = _hashSet.Count;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
finally
|
|
{
|
|
if (_lock.IsWriteLockHeld)
|
|
{
|
|
_lock.ExitWriteLock();
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion 原子操作方法
|
|
} |