@@ -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