BlockingCollection
或ConcurrentQueue
的正确用法是什么,这样您就可以自由地将项目出列,而不会使用线程消耗掉一半或更多的CPU
我用两个线程运行一些测试,除非我有一个线程。睡眠至少50~100ms,它总是会占用我50%的CPU。
这里有一个虚构的例子:
private void _DequeueItem()
{
object o = null;
while(socket.Connected)
{
while (!listOfQueueItems.IsEmpty)
{
if (listOfQueueItems.TryDequeue(out o))
{
// use the data
}
}
}
}
对于上面的例子,我必须设置一个thread.sleep,这样cpu就不会爆炸。
注意:我也尝试过,但没有检查IsEmpty,结果是一样的。
这不是因为BlockingCollection
或ConcurrentQueue
,而是while循环:
while(socket.Connected)
{
while (!listOfQueueItems.IsEmpty)
{ /*code*/ }
}
当然,它会降低cpu;因为如果队列是空的,那么while循环就像:
while (true) ;
这反过来又会消耗cpu资源。
这不是使用ConcurrentQueue
的好方法,您应该将AutoResetEvent
与之一起使用,这样无论何时添加项目,都会收到通知。示例:
private ConcurrentQueue<Data> _queue = new ConcurrentQueue<Data>();
private AutoResetEvent _queueNotifier = new AutoResetEvent(false);
//at the producer:
_queue.Enqueue(new Data());
_queueNotifier.Set();
//at the consumer:
while (true)//or some condition
{
_queueNotifier.WaitOne();//here we will block until receive signal notification.
Data data;
if (_queue.TryDequeue(out data))
{
//handle the data
}
}
为了更好地使用BlockingCollection
,您应该使用GetConsumingEnumerable()
来等待添加项目,例如:
//declare the buffer
private BlockingCollection<Data> _buffer = new BlockingCollection<Data>(new ConcurrentQueue<Data>());
//at the producer method:
_messageBuffer.Add(new Data());
//at the consumer
foreach (Data data in _buffer.GetConsumingEnumerable())//it will block here automatically waiting from new items to be added and it will not take cpu down
{
//handle the data here.
}
在这种情况下,您确实希望使用BlockingCollection
类。它被设计为阻止,直到某个项目出现在队列中。这种性质的集合通常被称为阻塞队列。这种特定的实现对于多个生产者和多个消费者来说是安全的。如果你自己尝试实现,这是一件很难做到的事情。以下是如果您使用BlockingCollection
,您的代码会是什么样子。
private void _DequeueItem()
{
while(socket.Connected)
{
object o = listOfQueueItems.Take();
// use the data
}
}
如果队列为空,Take
方法会自动阻止。它以将线程置于SleepWaitJoin
状态的方式进行阻塞,这样就不会消耗CPU资源。BlockingCollection
的巧妙之处在于它还使用了低锁定策略来提高性能。这意味着Take
将检查队列中是否有项目,如果没有,它将短暂地执行旋转等待以防止线程的上下文切换。如果队列仍然是空的,那么它将使线程进入睡眠状态。这意味着BlockingCollection
将具有ConcurrentQueue
在并发执行方面提供的一些性能优势。
只有当队列为空时才能调用Thread.Sleep()
:
private void DequeueItem()
{
object o = null;
while(socket.Connected)
{
if (listOfQueueItems.IsEmpty)
{
Thread.Sleep(50);
}
else if (listOfQueueItems.TryDequeue(out o))
{
// use the data
}
}
}
否则,您应该考虑使用事件。