使用 semaphore
、 Mutex
可以達到對 BlockQueue 的要求。單純使用 Lock
,能確保共用資源操作的原子性,卻無法達到通知生產者或消費者的目的。
所以接下來,來使用 .Net 之中,可以使用基於 EventWaitHandle
的 ManualResetEvent
或ManualResetEventSlim
,達到通知 BlockQueue 的特性。
可能會有人有疑問,AutoResetEvent 一樣是繼承 EventWaitHandle ,為何選擇 ManualResetEvent ,而不使用AutoResetEvent ?
ManualResetEvent | AutoResetEvent |
---|---|
若鎖定後,要再次使用前,必須先重置狀態。 | 若鎖定後,使用時,會自行重置狀態。 |
一次可以釋放所有鎖定的執行緒。 | 一次只能釋放一個執行緒。 |
而在 BlockQueue 之中,除非特別去記錄執行緒資訊,不然無法明確得知執行緒的數量。使用ManualRestEvent
可以一口氣釋放所有的執行緒,減少一筆筆釋放的麻煩。
同時,在 .Net Core
之中,還有一個 ManualRestEventSlim
的類別提供使用,它可視為輕量化的 ManualRestEvent
。
據官方的說法,ManualResetEventSlim
的執行效能高於 ManualRestEvent
,但是兩者間還是有些差異。
ManualResetEventSlim
只能在同一個 Process 範圍內作用;ManualResetEvent
則沒有這個限制。ManualResetEventSlim
的短時間的鎖定時,所花費的成本較低。public class BlockQueue<T> : LockQueue<T>
{
private ManualResetEventSlim _enqueueWait;
private ManualResetEventSlim _dequeueWait;
public BlockQueue()
{
_enqueueWait = new ManualResetEventSlim(false);
_dequeueWait = new ManualResetEventSlim(false);
}
public void Enqueue(T item)
{
while (true)
{
// 因為 LockQueue 的上限是 20, 偷懶寫法。
if (this.Count == 20)
{
_enqueueWait.Wait();
}
base.Enqueue(item);
// 準備 Enqueue 下次的 Wait
_enqueueWait.Reset();
// Dequeue 放行
_dequeueWait.Set();
}
}
public T Dequeue()
{
while (true)
{
if (base.IsEmpty == true)
{
_dequeueWait.Wait();
}
var dequeue = base.Dequeue();
_dequeueWait.Reset();
_enqueueWait.Set();
return dequeue;
}
}
}
測試程式,請見 Lock
章節。
不管是 Lock
、Semaphore
、Mutex
、ManualReset
,提到的都概念與簡單的實作練習。在 .Net Core
之中,己經針對生產者消費者模型,設計 IProducerConsumerCollection 介面。
像 BlockCollection
或 ConcurrentQueue
均為IProducerConsumerCollection
的實作,提供安全執行緒集合適用的封鎖和界限容量,
也因為 .Net core 之中,己經提供 System.Collections.Concurrent.ConcurrentQueue
這個更加完整的 Queue ,在往後的實作,改為使用 ConcurrentQueue
。