@@ -63,6 +63,7 @@ public void EvictExpired()
6363 //Eviction already started by another thread? forget it, lets move on
6464 if ( Monitor . TryEnter ( _cleanUpTimer ) ) //use the timer-object for our lock, it's local, private and instance-type, so its ok
6565 {
66+ List < TKey > evictedKeys = null ; // Batch eviction callbacks
6667 try
6768 {
6869 //cache current tick count in a var to prevent calling it every iteration inside "IsExpired()" in a tight loop.
@@ -75,15 +76,21 @@ public void EvictExpired()
7576 {
7677 if ( p . Value . IsExpired ( currTime ) ) //call IsExpired with "currTime" to avoid calling Environment.TickCount64 multiple times
7778 {
78- _dict . TryRemove ( p ) ;
79- OnEviction ( p . Key ) ;
79+ if ( _dict . TryRemove ( p ) && _itemEvicted != null ) // collect key for later batch processing (only if callback exists)
80+ {
81+ evictedKeys ??= new List < TKey > ( ) ; //lazy initialize the list
82+ evictedKeys . Add ( p . Key ) ;
83+ }
8084 }
8185 }
8286 }
8387 finally
8488 {
8589 Monitor . Exit ( _cleanUpTimer ) ;
8690 }
91+
92+ // Trigger batched eviction callbacks outside the loop to prevent flooding the thread pool
93+ OnEviction ( evictedKeys ) ;
8794 }
8895 }
8996
@@ -163,7 +170,7 @@ public bool TryGet(TKey key, out TValue value)
163170 *
164171 * */
165172
166- OnEviction ( key ) ;
173+ Task . Run ( ( ) => OnEviction ( key ) ) ;
167174
168175 return false ;
169176 }
@@ -203,7 +210,7 @@ private TValue GetOrAddCore(TKey key, Func<TValue> valueFactory, TimeSpan ttl)
203210 if ( ! wasAdded ) //performance hack: skip expiration check if a brand item was just added
204211 {
205212 if ( ttlValue . ModifyIfExpired ( valueFactory , ttl ) )
206- OnEviction ( key ) ;
213+ Task . Run ( ( ) => OnEviction ( key ) ) ;
207214 }
208215
209216 return ttlValue . Value ;
@@ -288,6 +295,25 @@ private void OnEviction(TKey key)
288295 } ) ;
289296 }
290297
298+ // same as OnEviction(TKey) but for batching
299+ private void OnEviction ( List < TKey > keys )
300+ {
301+ if ( keys == null || keys . Count == 0 ) return ;
302+ if ( _itemEvicted == null ) return ;
303+
304+ Task . Run ( ( ) => //run on thread pool to avoid blocking
305+ {
306+ try
307+ {
308+ foreach ( var key in keys )
309+ {
310+ _itemEvicted ( key ) ;
311+ }
312+ }
313+ catch { } //to prevent any exceptions from crashing the thread
314+ } ) ;
315+ }
316+
291317 private class TtlValue
292318 {
293319 public TValue Value { get ; private set ; }
0 commit comments