上一节介绍的是并发集合ConcurrentQueue,遇到的问题是当生产者产生数据向队列集合加数据,消费者去消耗这些数据,当队列中数据为空时候,就需要使用sleep来阻塞这个线程,有什么方法可以让集合直接带有阻塞功能呢?这个时候我们就可以使用BlockingCollection。
static void Main(string[] args)
{
int count = 0;
var queue = new ConcurrentQueue<string>();
// 生产者线程
Task.Factory.StartNew(() =>
{
while (true)
{
queue.Enqueue("value" + count);
count++;
}
});
// 消费者线程1
Task.Factory.StartNew(() =>
{
while (true)
{
string value;
if (queue.TryDequeue(out value))
{
Console.WriteLine("Worker 1: " + value);
}
}
});
// 消费者线程2
Task.Factory.StartNew(() =>
{
while (true)
{
string value;
if (queue.TryDequeue(out value))
{
Console.WriteLine("Worker 2: " + value);
}
}
});
Thread.Sleep(50000);
Console.ReadLine();
}
我们使用BlockingCollection
static void Main(string[] args)
{
int count = 0;
var blockingCollection = new BlockingCollection<string>();
Task.Factory.StartNew(() =>
{
while (true)
{
blockingCollection.Add("value" + count);
count++;
}
});
Task.Factory.StartNew(() =>
{
while (true)
{
Console.WriteLine("Worker 1: " + blockingCollection.Take());
}
});
Task.Factory.StartNew(() =>
{
while (true)
{
Console.WriteLine("Worker 2: " + blockingCollection.Take());
}
});
Thread.Sleep(50000);
Console.ReadLine();
}
BlockingCollection集合是一个拥有阻塞功能的集合,它就 是完成了经典生产者消费者的算法功能。它没有实现底层的存储结构,而是使用了实现IProducerConsumerCollection接口的几个集合 作为底层的数据结构,例如ConcurrentBag, ConcurrentStack或者是ConcurrentQueue。你可以在构造BlockingCollection实例的时候传入这个参数,如果 不指定的话,则默认使用ConcurrentQueue作为存储结构。
而对于生产者来说,只需要通过调用其Add方法放数据,消费者只需要调用Take方法来取数据就可以了。
当然了上面的消费者代码中还有一点是让人不爽的,那就是while语句,可以更优雅一点吗?答案还是肯定的:
static void Main(string[] args)
{
int count = 0;
var blockingCollection = new BlockingCollection<string>();
//生产者
Task.Factory.StartNew(() =>
{
while (true)
{
blockingCollection.Add("value" + count);
count++;
}
});
//消费者
Task.Factory.StartNew(() =>
{
while (true)
{
Console.WriteLine("Worker 1: " + blockingCollection.Take());
}
});
//消费者写法二
Task.Factory.StartNew(() =>
{
foreach (string value in blockingCollection.GetConsumingEnumerable())
{
Console.WriteLine("Worker 1: " + value);
}
});
Thread.Sleep(50000);
Console.ReadLine();
}
GetConsumingEnumerable()方法是关键,这个方法会遍历集合取出数据,一旦发现集合空了,则阻塞自己,直到集合中又有元素了再开始遍历,神奇吧。
有时候我们要控制一下最大队列数,这个时候我们就可以使用BoundedCapacity来阻止再向队列加数据
BoundedCapacity是用来限制集合的最大容量,当容量已满后,后续的添加操作会被阻塞,一旦有元素被移除,那么阻塞的添加操作会成功执行
//500就是最大容量
var blockingCollection = new BlockingCollection<string>(500);