diff --git a/src/EasyCaching.Core/EasyCachingAbstractProvider.cs b/src/EasyCaching.Core/EasyCachingAbstractProvider.cs index dffaf6dc..ae678b31 100644 --- a/src/EasyCaching.Core/EasyCachingAbstractProvider.cs +++ b/src/EasyCaching.Core/EasyCachingAbstractProvider.cs @@ -366,9 +366,62 @@ public async Task>> GetAllAsync(IEnumerable public async Task> GetAsync(string cacheKey, Func> dataRetriever, Func> expirationRetriever, CancellationToken cancellationToken = default) { - if (_lockFactory == null) return await BaseGetAsync(cacheKey, dataRetriever, expirationRetriever, cancellationToken); - // TiSvc 沒用到,不實作 - throw new NotImplementedException(); + var operationId = s_diagnosticListener.WriteGetCacheBefore(new BeforeGetRequestEventData(CachingProviderType.ToString(), Name, nameof(GetAsync), new[] { cacheKey })); + Exception e = null; + try + { + if (_lockFactory == null) return await BaseGetAsync(cacheKey, dataRetriever, expirationRetriever, cancellationToken); + + var value = await BaseGetAsync(cacheKey, cancellationToken); + if (value.HasValue) return value; + + var @lock = _lockFactory.CreateLock(Name, $"{cacheKey}_Lock"); + try + { + if (!await @lock.LockAsync(_options.SleepMs, cancellationToken)) throw new TimeoutException(); + + value = await BaseGetAsync(cacheKey, cancellationToken); + if (value.HasValue) return value; + + var task = dataRetriever(); + if (!task.IsCompleted && + await Task.WhenAny(task, Task.Delay(_options.LockMs, cancellationToken)) != task) + throw new TimeoutException(); + + var item = await task; + if (item != null || _options.CacheNulls) + { + TimeSpan expiration = await expirationRetriever(); + await BaseSetAsync(cacheKey, item, expiration, cancellationToken); + + return new CacheValue(item, true); + } + else + { + return CacheValue.NoValue; + } + } + finally + { + await @lock.DisposeAsync(); + } + } + catch (Exception ex) + { + e = ex; + throw; + } + finally + { + if (e != null) + { + s_diagnosticListener.WriteGetCacheError(operationId, e); + } + else + { + s_diagnosticListener.WriteGetCacheAfter(operationId); + } + } } public async Task> GetAsync(string cacheKey, Func> dataRetriever, TimeSpan expiration, CancellationToken cancellationToken = default)