现在的位置: 首页 > 综合 > 正文

.NET(C#):使用线程安全集合快速解决生产者消费者问题

2013年03月12日 ⁄ 综合 ⁄ 共 2625字 ⁄ 字号 评论关闭

程序演示:

进入程序,首先得输入最大容量,比如输入10:

image

 

接下来生产线生成了,最初当然只有0个项目,我们现在开始生产5个项目,在“生产数量”中输入5,如下图:

image

 

然后点击“生产”,5个项目就被生产了:

image

 

接下来我们再生产12个项目,加上之前的5个项目,此时一共会生产17个项目,但是总容量是10,所以会有7个生产项目要等待(线程阻塞):

image

 

接着再去生产10个项目,此时已经有7个生产项目在等待状态,后续的生产项目需求会叠加:

image

 

现在的情况是:有10个项目已经被生产,还有17个项目等待被生产,下面我们消费5个项目,结果如下:

image

 

当5个项目被消费后,等待生产的项目立即开始生产,知道容量又一次到最大值10,而左面等待生产的项目个数变成了12(减少了5个)。

下面直接消费100个,结果:

image

 

由于之前有10个被生产,12个在等待生产,所以等着22个项目被消费后,后续的消费无法满足,因此有78个消费在等待。

 

最后再生产80个项目,其中78个会被消费,然后剩2个留在生产线上,如下图:

image

 

 

生产着消费者问题有非常多的解决方式,不过使用.NET 4.0+的线程安全集合解决起来非常简单。用户不需要自定义线程同步逻辑,直接使用线程安全集合,然后多线程去操作就可以了,当然,更具体些,使用BlockingCollection,它本身不是严格意义上的集合,而是包转另一个线程安全集合使他具备阻塞功能。使用BlockingCollection来定义整个生产线集合。接着生产和消费问题就是在这个阻塞集合中进行添加和删除操作,这里我们会继承BlockingCollection得天独厚的优点,当向已满的集合中添加值,或者向空集合中删除值都会造成操作线程阻塞,知道该需求被完成,否则操作线程一直阻塞下去。

 

整个执行上核心就两个类:Operator和RequestItem。

Operator包含着BlockingCollection,并提供封装方法来调用BlockingCollection的Add和Take来进行生产和消费操作。

任何操作都被RequestItem来代表,这个类型存储着操作次数,就是“生产5个项目”中的5。对于可以完成的命令,BlockingCollection显然不会阻塞,然后根据操作次数执行生产或者消费操作,最后RequestItem会被删除掉。但是如果命令无法得到满足,此时BlockCollection会阻塞,相应的RequestItem也会留下,操作次数则代表等待的工作数,同时RequestItem也可以叠加。

 

可以参考下图:

image

 

执行上差不多这些,在界面更新上,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

抱歉!评论已关闭.