C# 创建阻塞队列<;T>;在.NET中?
我有一个场景,我有多个线程添加到一个队列,多个线程从同一个队列读取。如果队列达到特定大小,则在添加时将阻止正在填充队列的所有线程,直到从队列中删除项目 下面的解决方案是我现在正在使用的,我的问题是:如何改进?在我应该使用的BCL中是否有一个已经启用此行为的对象C# 创建阻塞队列<;T>;在.NET中?,c#,.net,multithreading,collections,queue,C#,.net,Multithreading,Collections,Queue,我有一个场景,我有多个线程添加到一个队列,多个线程从同一个队列读取。如果队列达到特定大小,则在添加时将阻止正在填充队列的所有线程,直到从队列中删除项目 下面的解决方案是我现在正在使用的,我的问题是:如何改进?在我应该使用的BCL中是否有一个已经启用此行为的对象 internal class BlockingCollection<T> : CollectionBase, IEnumerable { //todo: might be worth changing this int
internal class BlockingCollection<T> : CollectionBase, IEnumerable
{
//todo: might be worth changing this into a proper QUEUE
private AutoResetEvent _FullEvent = new AutoResetEvent(false);
internal T this[int i]
{
get { return (T) List[i]; }
}
private int _MaxSize;
internal int MaxSize
{
get { return _MaxSize; }
set
{
_MaxSize = value;
checkSize();
}
}
internal BlockingCollection(int maxSize)
{
MaxSize = maxSize;
}
internal void Add(T item)
{
Trace.WriteLine(string.Format("BlockingCollection add waiting: {0}", Thread.CurrentThread.ManagedThreadId));
_FullEvent.WaitOne();
List.Add(item);
Trace.WriteLine(string.Format("BlockingCollection item added: {0}", Thread.CurrentThread.ManagedThreadId));
checkSize();
}
internal void Remove(T item)
{
lock (List)
{
List.Remove(item);
}
Trace.WriteLine(string.Format("BlockingCollection item removed: {0}", Thread.CurrentThread.ManagedThreadId));
}
protected override void OnRemoveComplete(int index, object value)
{
checkSize();
base.OnRemoveComplete(index, value);
}
internal new IEnumerator GetEnumerator()
{
return List.GetEnumerator();
}
private void checkSize()
{
if (Count < MaxSize)
{
Trace.WriteLine(string.Format("BlockingCollection FullEvent set: {0}", Thread.CurrentThread.ManagedThreadId));
_FullEvent.Set();
}
else
{
Trace.WriteLine(string.Format("BlockingCollection FullEvent reset: {0}", Thread.CurrentThread.ManagedThreadId));
_FullEvent.Reset();
}
}
}
内部类BlockingCollection:CollectionBase,IEnumerable
{
//todo:可能值得将此更改为适当的队列
私有自动恢复事件_FullEvent=新自动恢复事件(false);
内部T此[int i]
{
获取{return(T)List[i];}
}
私有int_MaxSize;
内部整数最大值
{
获取{return\u MaxSize;}
设置
{
_MaxSize=值;
checkSize();
}
}
内部BlockingCollection(int maxSize)
{
MaxSize=MaxSize;
}
内部作废添加(T项)
{
WriteLine(string.Format(“BlockingCollection添加等待:{0}”,Thread.CurrentThread.ManagedThreadId));
_FullEvent.WaitOne();
列表。添加(项目);
WriteLine(string.Format(“添加的BlockingCollection项:{0}”,Thread.CurrentThread.ManagedThreadId));
checkSize();
}
内部空隙清除(T项)
{
锁(列表)
{
列表。删除(项目);
}
WriteLine(string.Format(“BlockingCollection项已删除:{0}”,Thread.CurrentThread.ManagedThreadId));
}
受保护的覆盖无效OnRemoveComplete(int索引,对象值)
{
checkSize();
基于RemoveComplete(索引、值);
}
内部新IEnumerator GetEnumerator()
{
返回List.GetEnumerator();
}
私有void checkSize()
{
如果(计数<最大值)
{
WriteLine(string.Format(“BlockingCollection FullEvent set:{0}”,Thread.CurrentThread.ManagedThreadId));
_FullEvent.Set();
}
其他的
{
WriteLine(string.Format(“BlockingCollection FullEvent reset:{0}”,Thread.CurrentThread.ManagedThreadId));
_FullEvent.Reset();
}
}
}
如果您希望获得最大吞吐量,允许多个读卡器读取,而只允许一个写卡器写入,BCL有一种称为ReaderWriterLockSlim的东西,可以帮助精简代码…看起来非常不安全(几乎没有同步);比如说:
class SizeQueue<T>
{
private readonly Queue<T> queue = new Queue<T>();
private readonly int maxSize;
public SizeQueue(int maxSize) { this.maxSize = maxSize; }
public void Enqueue(T item)
{
lock (queue)
{
while (queue.Count >= maxSize)
{
Monitor.Wait(queue);
}
queue.Enqueue(item);
if (queue.Count == 1)
{
// wake up any blocked dequeue
Monitor.PulseAll(queue);
}
}
}
public T Dequeue()
{
lock (queue)
{
while (queue.Count == 0)
{
Monitor.Wait(queue);
}
T item = queue.Dequeue();
if (queue.Count == maxSize - 1)
{
// wake up any blocked enqueue
Monitor.PulseAll(queue);
}
return item;
}
}
}
好的,您可以看看
System.Threading.Semaphore
类。除此之外-不,你必须自己做这个。恐怕没有这样的内置收藏。我还没有完全探索过,但它们可能有适合你需要的东西,或者至少有一些反光材料可以从中获得灵感
希望这能有所帮助。“如何改进?”
您需要查看类中的每一个方法,并考虑如果另一个线程同时调用该方法或任何其他方法会发生什么。例如,您在Remove方法中放置了锁,但在Add方法中没有。如果一个线程在另一个线程删除的同时添加,会发生什么情况?坏事
还考虑一个方法可以返回第二个对象,该对象提供对第一个对象的内部数据的访问,例如,GetEnumerator。假设一个线程正在通过该枚举器,另一个线程正在同时修改列表。不太好
一个很好的经验法则是,通过将类中的方法数量减少到绝对最小值,使这一点变得更简单 特别是,不要继承另一个容器类,因为您将公开该类的所有方法,从而为调用者提供损坏内部数据或查看部分完整数据更改的方法(同样糟糕,因为此时数据似乎已损坏)。隐藏所有细节,并对如何允许访问这些细节毫不留情 我强烈建议您使用现成的解决方案——买一本关于线程的书或使用第三方库。否则,考虑到您正在尝试的内容,您将在很长一段时间内调试代码 另外,如果Remove返回一个项目(比如,首先添加的项目,因为它是一个队列),而不是调用方选择一个特定的项目,这难道不是更有意义吗?当队列为空时,也许Remove也应该阻塞更新:马克的回答实际上实现了所有这些建议!:)但我将把这个留在这里,因为这可能有助于理解为什么他的版本会有如此大的改进。这就是我为线程安全的有界阻塞队列所做的工作
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
public class BlockingBuffer<T>
{
private Object t_lock;
private Semaphore sema_NotEmpty;
private Semaphore sema_NotFull;
private T[] buf;
private int getFromIndex;
private int putToIndex;
private int size;
private int numItems;
public BlockingBuffer(int Capacity)
{
if (Capacity <= 0)
throw new ArgumentOutOfRangeException("Capacity must be larger than 0");
t_lock = new Object();
buf = new T[Capacity];
sema_NotEmpty = new Semaphore(0, Capacity);
sema_NotFull = new Semaphore(Capacity, Capacity);
getFromIndex = 0;
putToIndex = 0;
size = Capacity;
numItems = 0;
}
public void put(T item)
{
sema_NotFull.WaitOne();
lock (t_lock)
{
while (numItems == size)
{
Monitor.Pulse(t_lock);
Monitor.Wait(t_lock);
}
buf[putToIndex++] = item;
if (putToIndex == size)
putToIndex = 0;
numItems++;
Monitor.Pulse(t_lock);
}
sema_NotEmpty.Release();
}
public T take()
{
T item;
sema_NotEmpty.WaitOne();
lock (t_lock)
{
while (numItems == 0)
{
Monitor.Pulse(t_lock);
Monitor.Wait(t_lock);
}
item = buf[getFromIndex++];
if (getFromIndex == size)
getFromIndex = 0;
numItems--;
Monitor.Pulse(t_lock);
}
sema_NotFull.Release();
return item;
}
}
使用系统;
使用System.Collections.Generic;
使用系统文本;
使用系统线程;
公共类阻止缓冲区
{
私有对象t_锁;
私有信号量sema_NotEmpty;
私有信号量信号未满;
私人T[]buf;
私有int-getFromIndex;
私人int putToIndex;
私有整数大小;
私有内部网络;
公共阻止缓冲区(整数容量)
{
如果(容量我刚刚用被动扩展完成了这个,并记住了这个问题:
public class BlockingQueue<T>
{
private readonly Subject<T> _queue;
private readonly IEnumerator<T> _enumerator;
private readonly object _sync = new object();
public BlockingQueue()
{
_queue = new Subject<T>();
_enumerator = _queue.GetEnumerator();
}
public void Enqueue(T item)
{
lock (_sync)
{
_queue.OnNext(item);
}
}
public T Dequeue()
{
_enumerator.MoveNext();
return _enumerator.Current;
}
}
公共类阻止队列
{
私有只读主题队列;
私有只读IEnumerator\u枚举器;
私有只读对象_sync=新对象();
公共阻止队列()
{
_队列=新主题();
_枚举器=_queue.GetEnumerator();
}
公共无效排队(T项)
{
锁定(同步)
{
_queue.OnNext(项目);
}
}
公共T出列()
{
_枚举数。MoveNext();
返回_enumerator.Current;
}
}
不一定完全安全,但非常简单。使用.net 4 BlockingCollection,使用Add()排队,使用Take()排队。它在内部使用非blocki
public class BlockingQueue<T>
{
private readonly Subject<T> _queue;
private readonly IEnumerator<T> _enumerator;
private readonly object _sync = new object();
public BlockingQueue()
{
_queue = new Subject<T>();
_enumerator = _queue.GetEnumerator();
}
public void Enqueue(T item)
{
lock (_sync)
{
_queue.OnNext(item);
}
}
public T Dequeue()
{
_enumerator.MoveNext();
return _enumerator.Current;
}
}
public class ProducerConsumerQueue<T> : BlockingCollection<T>
{
/// <summary>
/// Initializes a new instance of the ProducerConsumerQueue, Use Add and TryAdd for Enqueue and TryEnqueue and Take and TryTake for Dequeue and TryDequeue functionality
/// </summary>
public ProducerConsumerQueue()
: base(new ConcurrentQueue<T>())
{
}
/// <summary>
/// Initializes a new instance of the ProducerConsumerQueue, Use Add and TryAdd for Enqueue and TryEnqueue and Take and TryTake for Dequeue and TryDequeue functionality
/// </summary>
/// <param name="maxSize"></param>
public ProducerConsumerQueue(int maxSize)
: base(new ConcurrentQueue<T>(), maxSize)
{
}
}