程序演示:
进入程序,首先得输入最大容量,比如输入10:
接下来生产线生成了,最初当然只有0个项目,我们现在开始生产5个项目,在“生产数量”中输入5,如下图:
然后点击“生产”,5个项目就被生产了:
接下来我们再生产12个项目,加上之前的5个项目,此时一共会生产17个项目,但是总容量是10,所以会有7个生产项目要等待(线程阻塞):
接着再去生产10个项目,此时已经有7个生产项目在等待状态,后续的生产项目需求会叠加:
现在的情况是:有10个项目已经被生产,还有17个项目等待被生产,下面我们消费5个项目,结果如下:
当5个项目被消费后,等待生产的项目立即开始生产,知道容量又一次到最大值10,而左面等待生产的项目个数变成了12(减少了5个)。
下面直接消费100个,结果:
由于之前有10个被生产,12个在等待生产,所以等着22个项目被消费后,后续的消费无法满足,因此有78个消费在等待。
最后再生产80个项目,其中78个会被消费,然后剩2个留在生产线上,如下图:
生产着消费者问题有非常多的解决方式,不过使用.NET 4.0+的线程安全集合解决起来非常简单。用户不需要自定义线程同步逻辑,直接使用线程安全集合,然后多线程去操作就可以了,当然,更具体些,使用BlockingCollection,它本身不是严格意义上的集合,而是包转另一个线程安全集合使他具备阻塞功能。使用BlockingCollection来定义整个生产线集合。接着生产和消费问题就是在这个阻塞集合中进行添加和删除操作,这里我们会继承BlockingCollection得天独厚的优点,当向已满的集合中添加值,或者向空集合中删除值都会造成操作线程阻塞,知道该需求被完成,否则操作线程一直阻塞下去。
整个执行上核心就两个类:Operator和RequestItem。
Operator包含着BlockingCollection,并提供封装方法来调用BlockingCollection的Add和Take来进行生产和消费操作。
任何操作都被RequestItem来代表,这个类型存储着操作次数,就是“生产5个项目”中的5。对于可以完成的命令,BlockingCollection显然不会阻塞,然后根据操作次数执行生产或者消费操作,最后RequestItem会被删除掉。但是如果命令无法得到满足,此时BlockCollection会阻塞,相应的RequestItem也会留下,操作次数则代表等待的工作数,同时RequestItem也可以叠加。
可以参考下图:
执行上差不多这些,在界面更新上,Operator和RequestItem均继承INotifyPropertyChanged接口来通知WPF属性变化,同时生产和消费等待队列被存到Operator中的ObservableCollection中,有点像MVVM中的ViewModel,但程序没有用严格的MVVM模式来写。
Operator类型:
Consumers属性代表消费等待队列。
Producers属性代表生产等待队列。
Count和Capacity分别代表当前生产线项目个数和总容量。注意由于总容量Capacity不会变因此没必要像Count属性那样进行改变通知。
public ObservableCollection<RequestItem> Consumers { get; private set; }
public ObservableCollection<RequestItem> Producers { get; private set; }
public int Capacity { get; private set; }
#region Count
private int _Count = 0;
public int Count
{
get { return _Count; }
protected set
{
if (_Count != value)
{
_Count = value;
OnPropertyChanged("Count");
}
}
}
RequestItem则更简单了,只有一个Count属性代表操作数。
最后生产或者消费操作的执行就顺水推舟了,我们以生产方法:Produce来示例(代码中有注释):
public void Produce(int count)
{
//用另一个线程来操作BlockingCollection
Task.Factory.StartNew(() =>
{
//生成RequestItem
var item = new RequestItem(count);
//将RequestItem加入到生产等待队列中
//UIAction方法是用WPF间接调用WPF Dispatcher的Invoke相关操作
UIAction(() => Producers.Add(item));
//尝试进行生产操作
for (int i = 0; i < count; i++)
{
//调用内部BlockingCollection的Add方法
blockingQueue.Add(0);
//重要:此时代码运行到这说明没有阻塞,那么生产成功
UIAction(() =>
{
//将等待操作数减1
--item.Count;
//将生产线项目个数加1
++Count;
});
}
//所有生产需求均被完成,将此生产需求从队列中移除
UIAction(() => Producers.Remove(item));
});
}
OK,程序整体框架差不多是这样,还有一些细节和界面上处理就不讲了,有需要的话读者可以参考源代码。
源代码下载
下载地址
注意:此为微软SkyDrive存档,请用浏览器直接下载,用某些下载工具可能无法下载
源代码环境:Microsoft Visual C# 2010 Express