C# 创建阻塞队列<;T>;在.NET中?

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

我有一个场景,我有多个线程添加到一个队列,多个线程从同一个队列读取。如果队列达到特定大小,则在添加时将阻止正在填充队列的所有线程,直到从队列中删除项目

下面的解决方案是我现在正在使用的,我的问题是:如何改进?在我应该使用的BCL中是否有一个已经启用此行为的对象

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)
    {
    }



}