如果要在同一個 process 內實作「生產者與消費者(Producer-Consumer)」模式,最簡單的方式就是直接使用 .NET 內建的類別即可。
早期的 生產者 & 消費者 模式的物件。
優點
Add 與多個消費者呼叫 GetConsumingEnumerable,底層都已經是實作成 Thread-safe 的了。缺點
個人經驗
在舊系統的架構裡蠻常看到像 Windows Service 這種背景處理程式實作這種模式。如果這個 service 很輕量(單純的只做某一、二個職責工作),那只要把 server core 數量跟生產者的 worker 數量調配好,可以算是一個簡單粗暴的實作。
隨者 async/await 的非同步實作成為主流,後來 .net 就再推出了 System.Threading.Channels。
優點
foreach 配上 ReadAllAsync 方法,即可方便的實作出非同步的消費機制。或是用 WaitToReadAsync 先等待訊號,再用 TryRead 批次取出來處理。這代表當沒有 Job 需要處理的時候,thread 會先被還回去 thread pool,然後去處理別的事情,而不會整個閒置在那邊。ChannelOptions 的來設定 SingleWriter 或 SingleReader,也就是可以直接告訴程式是否只有「單一個」生產者 or 消費者。好處是在 enqueue or dequeue 的時候,底層就「不用」再去考慮 thread-safe 的問題(因為只有一條 thread 在做),那就可以省掉一些麻煩的上鎖機制,能夠在效率更好的情形下吃更多的量。個人經驗
在新實作的功能或專案裡,儘量都直接以 Channel 來進行實作。因為在一樣的前提之下,「理論」上 Channel 應該都會比 BlockingCollection 能夠吃下更多的量。
參考資料
筆記:.NET 併發處理 Async/Await 筆記
現在 C#:AI 時代的開發者修煉
Concurrency in C# Cookbook