简体   繁体   English

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

[英]Simple producer-consumer collection without BlockingCollection

I want to write a simple producer-consumer queue, without using the built-in System.Collections.Concurrent.BlockingCollection . 我想编写一个简单的生产者-消费者队列,而不使用内置的System.Collections.Concurrent.BlockingCollection Here's a quick attempt that "seems" to work. 这是一种“似乎”有效的快速尝试。 Is there anything wrong with it threading-wise, race conditions, deadlocks etc.? 它在线程方向,竞争条件,死锁等方面有什么问题吗?

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;
    }
}

Btw. 顺便说一句。 the only two methods I need are Add and TryTake , I don't need IEnumerable etc. 我唯一需要的两个方法是AddTryTake ,我不需要IEnumerable等。

I think using both lock and a ManualResetEvent is redundant. 我认为同时使用lockManualResetEvent是多余的。 I suggest you read more about ManualResetEvent on how to enter and exit synchronized areas in your code (you can also take a look at other synchronization mechanisms available under System.Threading ). 我建议您阅读有关ManualResetEvent的更多信息,以了解如何进入和退出代码中的同步区域(也可以查看System.Threading下可用的其他同步机制)。

If it's not just for exercise, you can also take a look at NetMQ . 如果不仅仅为了锻炼,还可以看看NetMQ

Hope it helps! 希望能帮助到你!

Microsoft recently dropped System.Threading.Channels , which is intended to provide optimized producer/consumer APIs, which may be a good fit in this case. 微软最近删除了System.Threading.Channels ,其目的是提供优化的生产者/消费者API,在这种情况下可能很合适。 It covers unbounded and bounded scenarios, and includes single plus multiple reader/writer scenarios. 它涵盖了无限制和无限制的方案,并且包括单个或多个读取器/写入器方案。 The API is pretty simple and intuitive to use; 该API使用起来非常简单直观。 the only slight caveat is that it uses an async -orientated API (for the consumer, and - in the case of bounded channels - for the producer). 唯一需要注意的一点是,它使用了面向async API(对于消费者,对于通道有限的情况,对于生产者)。

The point here being: the code you don't write tends to be code that has fewer pain points - especially if it was written by a team with expertise and interest in the specific problems being targeted. 这里的重点是:您不编写的代码往往是痛苦点更少的代码-尤其是如果它是由具有专业知识并且对特定问题感兴趣的团队编写的。


However: you can do everything in your current code without needing the ManualResetEvent - lock in C# is just a wrapper around the simplest parts of Monitor , but Monitor also provides wait/pulse functionality: 但是:您可以在当前代码中完成所有操作而无需使用ManualResetEvent -C#中的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;
    }
}

As per my comment in the question, 根据我对问题的评论,

Here is a my proposed solution. 这是我建议的解决方案。

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;
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM