Files
懒得勤快 06afa589ea 新增ConcurrentHashQueue<T> 和 HashQueue<T> 类:
- `ConcurrentHashQueue<T>`:线程安全的唯一元素队列,支持基本操作、批量操作、自定义比较器及原子操作。
- `HashQueue<T>`:非线程安全的唯一元素队列,结合 `Queue` 和 `HashSet` 特性,支持基本操作和容量优化。
- 为上述队列类添加扩展方法(如 `EnqueueWithLimit`、`DequeueAll` 等)。
2025-11-21 16:55:48 +08:00

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 原子操作方法
}