///<summary> /// Gets or creates data ///</summary> ///<param name="key">cache key</param> ///<param name="createItem">delegate to create item</param> ///<returns>T</returns> publicstatic CacheItem<T> GetOrCreate(string key, Func<T> createItem) { if (_cacheData.ContainsKey(key) == false) { //// 快取用[] = 更接近 Create or Update 的狀態不會因為誤判噴 Exception //// System.ArgumentException: An item with the same key has already been added. _cacheData[key] = new CacheItem<T>(createItem()); }
Console.WriteLine($"key : {key}, data : {_cacheData[key]}"); return _cacheData[key]; } }
快取測試 Endpoint
1 2 3 4 5 6 7 8 9
publicstaticvoidMapCacheTestEndpoints(this IEndpointRouteBuilder app) { app.MapGet("/SimpleCacheTest", () => { var data = CacheService<string>.GetOrCreate(123, () => YoyoDB.GetUserInfo(123)); var data2 = CacheService<string>.GetOrCreate(456, () => YoyoDB.GetUserInfo(456)); returnnew Tuple<CacheItem<string>, CacheItem<string>>(data,data2); }); }
測試畫面(GET 請求)
Thread-Safe?
在 Web API 或多執行緒程式中,可能會有多個請求「同時」存取快取,因此發生同時檢查 ContainsKey() 為 false,然後同時執行 _cacheData[key] = ...,結果會出現「重複寫入」或 Key already exists 的例外!
app.MapGet("RunCacheRaceConditionTest", () => { var tasks = new List<Task>();
//// 建立 20 個平行 Task,「同一時間」有很多執行緒跑同一段程式 for (int i = 0; i < 20;i++) { tasks.Add(Task.Run(() => { var data3 = CacheService<string>.GetOrCreate(1, () => YoyoDB.GetUserInfo(1)); return data3; })); }
Task.WaitAll(tasks.ToArray()); });
1 2 3
System.InvalidOperationException: Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state.
app.MapGet("/TreadSafeCacheWithTTLTest", () => { var tasks = new List<Task>(); for (int i = 0; i < 5; i ++) { tasks.Add(Task.Run(() => { var data5 = ThreadSafeCacheService<string>.GetOrCreate(1, () => YoyoDB.GetUserInfo(1), TimeSpan.FromSeconds(2)); }));