无需BlockingCollection的简单生产者-消费者集合

时间:2018-12-17 08:16:20

标签: c# multithreading producer-consumer

我想编写一个简单的生产者-消费者队列,而不使用内置的System.Collections.Concurrent.BlockingCollection。这是一种“似乎”有效的快速尝试。它在线程方向,竞争条件,死锁等方面有什么问题吗?

class ProducerConsumerQueue<T>
{
    Queue<T> Queue = new Queue<T>();
    ManualResetEvent Event = new ManualResetEvent(false);
    object Lock = new object();

    public void Add(T t)
    {
        lock (Lock)
        {
            Queue.Enqueue(t);
        }
        Event.Set();
    }

    public bool TryTake(out T t, int timeout)
    {
        if (Event.WaitOne(timeout))
        {
            lock (Lock)
            {
                if (Queue.Count > 0)
                {
                    t = Queue.Dequeue();
                    if (Queue.Count == 0) Event.Reset();
                    return true;
                }
            }
        }
        t = default(T);
        return false;
    }
}

顺便说一句。我唯一需要的两种方法是AddTryTake,我不需要IEnumerable等。

3 个答案:

答案 0 :(得分:1)

我认为同时使用lockManualResetEvent是多余的。建议您阅读有关ManualResetEvent的更多信息,以了解如何进入和退出代码中的同步区域(也可以查看System.Threading下可用的其他同步机制)。

如果不只是为了运动,还可以看看NetMQ

希望有帮助!

答案 1 :(得分:1)

Microsoft最近删除了System.Threading.Channels,其目的是提供优化的生产者/消费者API,在这种情况下可能很合适。它涵盖了无限制和无限制的方案,并且包括单个或多个读取器/写入器方案。该API使用起来非常简单直观。唯一需要注意的一点是,它使用面向async的API(针对消费者,对于渠道有限的情况,针对生产者)。

这里的要点是:您不编写的代码往往是痛苦点更少的代码-尤其是如果它是由具有专业知识并且对特定问题感兴趣的团队编写的。


但是:您可以在当前代码中完成所有操作,而无需使用C#中的ManualResetEvent-lock只是Monitor最简单部分的包装,但是Monitor还提供了等待/脉冲功能:

class ProducerConsumerQueue<T>
{
    private readonly Queue<T> Queue = new Queue<T>();

    public void Add(T t)
    {
        lock (Queue)
        {
            Queue.Enqueue(t);
            if (Queue.Count == 1)
            {
                // wake up one sleeper
                Monitor.Pulse(Queue);
            }
        }
    }

    public bool TryTake(out T t, int millisecondsTimeout)
    {
        lock (Queue)
        {
            if (Queue.Count == 0)
            {
                // try and wait for arrival
                Monitor.Wait(Queue, millisecondsTimeout);
            }
            if (Queue.Count != 0)
            {
                t = Queue.Dequeue();
                return true;
            }
        }
        t = default(T);
        return false;
    }
}

答案 2 :(得分:0)

根据我对问题的评论,

这是我建议的解决方案。

public class BlockingQueue<T>
{
    // In order to get rid of Lock object
    // Any thread should be able to add items to the queue
    private readonly ConcurrentQueue<T> _queue = new ConcurrentQueue<T>();

    // Only one thread is able to consume from queue
    // You can fine tune this to your interest
    private readonly SemaphoreSlim _slim = new SemaphoreSlim(1,1);

    public void Add(T item) {
        _queue.Enqueue(item);
    }

    public bool TryTake(out T item, TimeSpan timeout) {
        if (_slim.Wait(timeout)){
            return _queue.TryDequeue(out item);
        }
        item = default(T);
        return false;
    }
}