@@ -15,13 +15,22 @@ public class FastCache<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>,
1515 private readonly ConcurrentDictionary < TKey , TtlValue > _dict = new ConcurrentDictionary < TKey , TtlValue > ( ) ;
1616
1717 private readonly Timer _cleanUpTimer ;
18+ private readonly EvictionCallback _itemEvicted ;
19+
20+ /// <summary>
21+ /// Callback (RUNS ON THREAD POOL!) when an item is evicted from the cache.
22+ /// </summary>
23+ /// <param name="key"></param>
24+ public delegate void EvictionCallback ( TKey key ) ;
1825
1926 /// <summary>
2027 /// Initializes a new empty instance of <see cref="FastCache{TKey,TValue}"/>
2128 /// </summary>
2229 /// <param name="cleanupJobInterval">cleanup interval in milliseconds, default is 10000</param>
23- public FastCache ( int cleanupJobInterval = 10000 )
30+ /// <param name="itemEvicted">Optional callback (RUNS ON THREAD POOL!) when an item is evicted from the cache</param>
31+ public FastCache ( int cleanupJobInterval = 10000 , EvictionCallback itemEvicted = null )
2432 {
33+ _itemEvicted = itemEvicted ;
2534 _cleanUpTimer = new Timer ( s => { _ = EvictExpiredJob ( ) ; } , null , cleanupJobInterval , cleanupJobInterval ) ;
2635 }
2736
@@ -65,7 +74,10 @@ public void EvictExpired()
6574 foreach ( var p in _dict )
6675 {
6776 if ( p . Value . IsExpired ( currTime ) ) //call IsExpired with "currTime" to avoid calling Environment.TickCount64 multiple times
77+ {
6878 _dict . TryRemove ( p ) ;
79+ OnEviction ( p . Key ) ;
80+ }
6981 }
7082 }
7183 finally
@@ -151,6 +163,8 @@ public bool TryGet(TKey key, out TValue value)
151163 *
152164 * */
153165
166+ OnEviction ( key ) ;
167+
154168 return false ;
155169 }
156170
@@ -188,7 +202,8 @@ private TValue GetOrAddCore(TKey key, Func<TValue> valueFactory, TimeSpan ttl)
188202 //since TtlValue is a reference type we can update its properties in-place, instead of removing and re-adding to the dictionary (extra lookups)
189203 if ( ! wasAdded ) //performance hack: skip expiration check if a brand item was just added
190204 {
191- ttlValue . ModifyIfExpired ( valueFactory , ttl ) ;
205+ if ( ttlValue . ModifyIfExpired ( valueFactory , ttl ) )
206+ OnEviction ( key ) ;
192207 }
193208
194209 return ttlValue . Value ;
@@ -259,6 +274,22 @@ IEnumerator IEnumerable.GetEnumerator()
259274 return this . GetEnumerator ( ) ;
260275 }
261276
277+ private void OnEviction ( TKey key )
278+ {
279+ if ( _itemEvicted == null ) return ;
280+
281+ Task . Run ( ( ) => //run on thread pool to avoid blocking
282+ {
283+ try
284+ {
285+ _itemEvicted ( key ) ;
286+ }
287+ catch {
288+ var i = 0 ;
289+ } //to prevent any exceptions from crashing the thread
290+ } ) ;
291+ }
292+
262293 private class TtlValue
263294 {
264295 public TValue Value { get ; private set ; }
@@ -278,14 +309,17 @@ public TtlValue(TValue value, TimeSpan ttl)
278309 /// <summary>
279310 /// Updates the value and TTL only if the item is expired
280311 /// </summary>
281- public void ModifyIfExpired ( Func < TValue > newValueFactory , TimeSpan newTtl )
312+ /// <returns>True if the item expired and was updated, otherwise false</returns>
313+ public bool ModifyIfExpired ( Func < TValue > newValueFactory , TimeSpan newTtl )
282314 {
283315 var ticks = Environment . TickCount64 ; //save to a var to prevent multiple calls to Environment.TickCount64
284316 if ( IsExpired ( ticks ) ) //if expired - update the value and TTL
285317 {
286318 TickCountWhenToKill = ticks + ( long ) newTtl . TotalMilliseconds ; //update the expiration time first for better concurrency
287319 Value = newValueFactory ( ) ;
320+ return true ;
288321 }
322+ return false ;
289323 }
290324 }
291325
0 commit comments