單例模式(Singleton Pattern)搭配非同步方法與驗證

單例模式(Singleton Pattern)或者稱為單一實例模式,是一種設計模式Design Pattern,以往我在寫這種設計模式的時候都是使用lock方式來處理解決,但基於時代的演化,C#已經進化了一堆新特性與語法糖,應該藉機會來練習一下。這篇文章裡面會用到static、task、private constructor、task、async、await等混合概念。

會取消採用lock的點是網路上的意見覺得這個會耗損很多運算資源(我個人的感覺是還好不會差很多,倒是拋棄lock寫法會讓程式碼變得更清爽一點),在這邊以取號機(叫號機)的概念為實作方式,並撰寫多重執行緒來進行驗證,最後證明取號機是否在多重呼叫的環境下,被單一實例規範成自動序列化成呼叫。

單一實例類別

下面這段程式碼裡面的重點是將類別sealed密封化、建構子private私有化,並透過System.Lazy使一個static readonly靜態欄位來初始化與保存NumberMachine的實例,其他的async與await寫法就不多說了。基本上這段程式碼在「實務上」的計數器基底應該依存於資料庫,這樣在可以在Web server concurrent環境下有更好的延展性。

public sealed class NumberMachine
{ //藉由.NET 4.0封裝成延遲載入
  private static readonly System.Lazy<NumberMachine> _oLazy = new Lazy<NumberMachine>(() => new NumberMachine());
  //建構子設成private防止讓外界生成新實例
  private NumberMachine() { }
  //開放取得NumberMachine實例的靜態方法
  public async static System.Threading.Tasks.Task<string> GetNumber(string cCaller)
  {
    NumberMachine oMachine = _oLazy.Value;
    int iTemp = await oMachine.AddCount();
    return $"{cCaller}取得號碼:{iTemp}";
  }
  //計數欄位(這個數值在應用時期應該是資料庫的某欄位或統計資料)
  private int iCount = 0;
  //累加方法(這個動作在應用時期應該相依於資料庫)
  private async System.Threading.Tasks.Task<int> AddCount()
  {
    iCount += 1;
    return await System.Threading.Tasks.Task.FromResult<int>(iCount);
  }
}

驗證單一實例是否正確運行(抽號碼牌)

下面的程式碼就是在Main方法中實驗,同時產生丟出多重執行緒(平行執行緒),觀察上面的單一實例類別是否有正確地幫我們序列化成單一呼叫。

public static void Main()
{
  for (int i = 1; i <= 10; i++)
  {
    string cCaller = $"第{i}執行緒";
    System.Threading.Tasks.Task.Run(() => {
      WriteLine($"{cCaller}已經呼叫。");
      var oTask = NumberMachine.GetNumber(cCaller);
      oTask.GetAwaiter().OnCompleted(() => {
        WriteLine($"{oTask.Result}");
      });
    });
  }
  Read();
}

產生結果如下,透過結果可以看出每次調用執行緒的順序皆不一樣,回傳的結果也不盡相同,但單一實例模式仍然正確地幫我們把所有的平行調用序列化成單一執行,取號的號碼不會重複。

第2執行緒已經呼叫。
第5執行緒已經呼叫。
第1執行緒已經呼叫。
第4執行緒已經呼叫。
第3執行緒已經呼叫。
第7執行緒已經呼叫。
第8執行緒已經呼叫。
第6執行緒已經呼叫。
第7執行緒取得號碼:4
第9執行緒已經呼叫。
第9執行緒取得號碼:9
第10執行緒已經呼叫。
第3執行緒取得號碼:3
第6執行緒取得號碼:7
第1執行緒取得號碼:2
第10執行緒取得號碼:10
第2執行緒取得號碼:6
第5執行緒取得號碼:8
第4執行緒取得號碼:1
第8執行緒取得號碼:5

相關連結

  1. 利用鎖定(Lock)來保持資源在多執行緒間安全的共用與複寫
  2. 利用安全且非獨占的方式,將檔案內容讀取或寫回
  3. Task非同步作業的等候與終結
C# Static Task PrivateConstructor Threading Task Async Await Lazy LazyLoad NumberMachine NumberCallingMachine