diff --git a/src/libraries/Microsoft.Extensions.Caching.Memory/src/MemoryCache.cs b/src/libraries/Microsoft.Extensions.Caching.Memory/src/MemoryCache.cs index a3d22ca18e2dc..55dd1217e65f9 100644 --- a/src/libraries/Microsoft.Extensions.Caching.Memory/src/MemoryCache.cs +++ b/src/libraries/Microsoft.Extensions.Caching.Memory/src/MemoryCache.cs @@ -5,6 +5,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Internal; @@ -230,44 +231,45 @@ private void SetEntry(CacheEntry entry) } } - StartScanForExpiredItems(utcNow); + StartScanForExpiredItemsIfNeeded(utcNow); } /// public bool TryGetValue(object key, out object result) { ValidateCacheKey(key); - CheckDisposed(); - result = null; DateTimeOffset utcNow = _options.Clock.UtcNow; - bool found = false; if (_entries.TryGetValue(key, out CacheEntry entry)) { // Check if expired due to expiration tokens, timers, etc. and if so, remove it. // Allow a stale Replaced value to be returned due to concurrent calls to SetExpired during SetEntry. - if (entry.CheckExpired(utcNow) && entry.EvictionReason != EvictionReason.Replaced) + if (!entry.CheckExpired(utcNow) || entry.EvictionReason == EvictionReason.Replaced) { - // TODO: For efficiency queue this up for batch removal - RemoveEntry(entry); - } - else - { - found = true; entry.LastAccessed = utcNow; result = entry.Value; // When this entry is retrieved in the scope of creating another entry, // that entry needs a copy of these expiration tokens. entry.PropagateOptions(CacheEntryHelper.Current); + + StartScanForExpiredItemsIfNeeded(utcNow); + + return true; + } + else + { + // TODO: For efficiency queue this up for batch removal + RemoveEntry(entry); } } - StartScanForExpiredItems(utcNow); + StartScanForExpiredItemsIfNeeded(utcNow); - return found; + result = null; + return false; } /// @@ -287,7 +289,7 @@ public void Remove(object key) entry.InvokeEvictionCallbacks(); } - StartScanForExpiredItems(); + StartScanForExpiredItemsIfNeeded(_options.Clock.UtcNow); } private void RemoveEntry(CacheEntry entry) @@ -306,18 +308,22 @@ private void EntryExpired(CacheEntry entry) { // TODO: For efficiency consider processing these expirations in batches. RemoveEntry(entry); - StartScanForExpiredItems(); + StartScanForExpiredItemsIfNeeded(_options.Clock.UtcNow); } // Called by multiple actions to see how long it's been since we last checked for expired items. // If sufficient time has elapsed then a scan is initiated on a background task. - private void StartScanForExpiredItems(DateTimeOffset? utcNow = null) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void StartScanForExpiredItemsIfNeeded(DateTimeOffset utcNow) { - // Since fetching time is expensive, minimize it in the hot paths - DateTimeOffset now = utcNow ?? _options.Clock.UtcNow; - if (_options.ExpirationScanFrequency < now - _lastExpirationScan) + if (_options.ExpirationScanFrequency < utcNow - _lastExpirationScan) { - _lastExpirationScan = now; + ScheduleTask(utcNow); + } + + void ScheduleTask(DateTimeOffset utcNow) + { + _lastExpirationScan = utcNow; Task.Factory.StartNew(state => ScanForExpiredItems((MemoryCache)state), this, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); } @@ -325,7 +331,7 @@ private void StartScanForExpiredItems(DateTimeOffset? utcNow = null) private static void ScanForExpiredItems(MemoryCache cache) { - DateTimeOffset now = cache._options.Clock.UtcNow; + DateTimeOffset now = cache._lastExpirationScan = cache._options.Clock.UtcNow; foreach (CacheEntry entry in cache._entries.Values) { if (entry.CheckExpired(now)) @@ -500,16 +506,20 @@ private void CheckDisposed() { if (_disposed) { - throw new ObjectDisposedException(typeof(MemoryCache).FullName); + Throw(); } + + static void Throw() => throw new ObjectDisposedException(typeof(MemoryCache).FullName); } private static void ValidateCacheKey(object key) { if (key == null) { - throw new ArgumentNullException(nameof(key)); + Throw(); } + + static void Throw() => throw new ArgumentNullException(nameof(key)); } } }