程式示範:
進入程式,首先得輸入最大容量,比如輸入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