C# 在队列中添加数据时通知线程

C# 在队列中添加数据时通知线程,c#,multithreading,thread-safety,queue,C#,Multithreading,Thread Safety,Queue,我有一个线程正在队列中添加数据,现在我希望在添加数据时通知另一个线程,以便它可以开始处理队列中的数据 一个选择是线程将连续轮询队列,查看计数是否大于零,但我认为这不是一个好方法,任何其他建议都将不胜感激 任何关于如何实现这一点的建议,我都使用.NETFramework3.5 如果我有两个线程,一个在执行q.Enqueue(data),另一个在执行q.dequeue(),在这种情况下,我需要管理锁吗?我认为阻塞收集比排队更好。除此之外,持续检查队列大小(并在其为零时暂停线程)是非常好的方法 顺便说

我有一个线程正在队列中添加数据,现在我希望在添加数据时通知另一个线程,以便它可以开始处理队列中的数据

一个选择是线程将连续轮询队列,查看计数是否大于零,但我认为这不是一个好方法,任何其他建议都将不胜感激

任何关于如何实现这一点的建议,我都使用.NETFramework3.5


如果我有两个线程,一个在执行
q.Enqueue(data)
,另一个在执行
q.dequeue()
,在这种情况下,我需要管理锁吗?

我认为阻塞收集比排队更好。除此之外,持续检查队列大小(并在其为零时暂停线程)是非常好的方法


顺便说一句,我们这里讨论的是生产者-消费者模式。我想你可以在谷歌上搜索其他方法。

你可以使用
ManualResetEvent
通知线程

ManualResetEvent e = new ManualResetEvent(false);
每次
q.enqueue()之后
do
e.Set()
并在处理线程中,使用
e.WaitOne()
等待项目


如果在循环内进行处理,则应该在
e.WaitOne()
之后立即执行
e.Reset()
,我不使用队列,因为我更愿意批处理它们。这在您必须打开/关闭(日志)文件、打开/关闭数据库时更有用。以下是我如何创建这样一个示例:

// J. van Langen
public abstract class QueueHandler<T> : IDisposable
{
    // some events to trigger.
    ManualResetEvent _terminating = new ManualResetEvent(false);
    ManualResetEvent _terminated = new ManualResetEvent(false);
    AutoResetEvent _needProcessing = new AutoResetEvent(false);

    // my 'queue'
    private List<T> _queue = new List<T>();

    public QueueHandler()
    {
        new Thread(new ThreadStart(() =>
        {
            // what handles it should wait on.
            WaitHandle[] handles = new WaitHandle[] { _terminating, _needProcessing };

            // while not terminating, loop (0 timeout)
            while (!_terminating.WaitOne(0))
            {
                // wait on the _terminating and the _needprocessing handle.
                WaitHandle.WaitAny(handles);

                // my temporay array to store the current items.
                T[] itemsCopy;

                // lock the queue
                lock (_queue)
                {
                    // create a 'copy'
                    itemsCopy = _queue.ToArray();

                    // clear the queue.
                    _queue.Clear();
                }

                if (itemsCopy.Length > 0)
                    HandleItems(itemsCopy);
            }

            // the thread is done.
            _terminated.Set();

        })).Start();
    }

    public abstract void HandleItems(T[] items);

    public void Enqueue(T item)
    {
        // lock the queue to add the item.
        lock (_queue)
            _queue.Add(item);

        _needProcessing.Set();
    }

    // batch
    public void Enqueue(IEnumerable<T> items)
    {
        // lock the queue to add multiple items.
        lock (_queue)
            _queue.AddRange(items);

        _needProcessing.Set();
    }

    public void Dispose()
    {
        // let the thread know it should stop.
        _terminating.Set();

        // wait until the thread is stopped.
        _terminated.WaitOne();
    }

}

使用
BlockingCollection
类。它的好处是,如果队列为空,
Take
方法会阻塞(不进行轮询)。它包含在.NET4.0+中,或者作为下载的一部分,或者甚至可能是。如果需要,可以使用类的以下未优化变体

public class BlockingCollection<T>
{
    private readonly Queue<T> m_Queue = new Queue<T>();

    public void Add(T item)
    {
        lock (m_Queue)
        {
            m_Queue.Enqueue(item);
            Monitor.Pulse(m_Queue);
        }
    }

    public T Take()
    {
        lock (m_Queue)
        {
            while (m_Queue.Count == 0)
            {
                Monitor.Wait(m_Queue);
            }
            return m_Queue.Dequeue();
        }
    }

    public bool TryTake(out T item)
    {
        item = default(T);
        lock (m_Queue)
        {
            if (m_Queue.Count > 0)
            {
                item = m_Queue.Dequeue();
            }
        }
        return item != null;
    }

}
公共类阻止集合
{
私有只读队列m_Queue=新队列();
公共作废新增(T项)
{
锁(m_队列)
{
m_队列。排队(项目);
监视器脉冲(m_队列);
}
}
公众不接受
{
锁(m_队列)
{
while(m_Queue.Count==0)
{
监视器。等待(m_队列);
}
返回m_Queue.Dequeue();
}
}
公共图书馆试镜(外T项目)
{
项目=默认值(T);
锁(m_队列)
{
如果(m_Queue.Count>0)
{
item=m_Queue.Dequeue();
}
}
退货项目!=空;
}
}

或使用
ManualResetEvent
AutoResetEvent
是的,您需要为队列设置锁。但是第二个线程不知道什么时候有东西被添加到队列中。如果不想轮询(queue.peek),可以使用事件触发第二个线程,但仍然需要锁定队列。看看我的例子,这是处理事件(无轮询),并锁定队列。哎哟,我非常确定BC是旧的8(感谢目前的详细解释ManualResetEvent从您的代码中对我来说是可以的。我建议改用ManualResetEvent来使用AutoResetEvent。ManualResetEvent.Reset()在WaitOne()和Reset()之间排队时可能会出错
public class BlockingCollection<T>
{
    private readonly Queue<T> m_Queue = new Queue<T>();

    public void Add(T item)
    {
        lock (m_Queue)
        {
            m_Queue.Enqueue(item);
            Monitor.Pulse(m_Queue);
        }
    }

    public T Take()
    {
        lock (m_Queue)
        {
            while (m_Queue.Count == 0)
            {
                Monitor.Wait(m_Queue);
            }
            return m_Queue.Dequeue();
        }
    }

    public bool TryTake(out T item)
    {
        item = default(T);
        lock (m_Queue)
        {
            if (m_Queue.Count > 0)
            {
                item = m_Queue.Dequeue();
            }
        }
        return item != null;
    }

}