@@ -63,6 +63,7 @@ public void EvictExpired()
63
63
//Eviction already started by another thread? forget it, lets move on
64
64
if ( Monitor . TryEnter ( _cleanUpTimer ) ) //use the timer-object for our lock, it's local, private and instance-type, so its ok
65
65
{
66
+ List < TKey > evictedKeys = null ; // Batch eviction callbacks
66
67
try
67
68
{
68
69
//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()
75
76
{
76
77
if ( p . Value . IsExpired ( currTime ) ) //call IsExpired with "currTime" to avoid calling Environment.TickCount64 multiple times
77
78
{
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
+ }
80
84
}
81
85
}
82
86
}
83
87
finally
84
88
{
85
89
Monitor . Exit ( _cleanUpTimer ) ;
86
90
}
91
+
92
+ // Trigger batched eviction callbacks outside the loop to prevent flooding the thread pool
93
+ OnEviction ( evictedKeys ) ;
87
94
}
88
95
}
89
96
@@ -163,7 +170,7 @@ public bool TryGet(TKey key, out TValue value)
163
170
*
164
171
* */
165
172
166
- OnEviction ( key ) ;
173
+ Task . Run ( ( ) => OnEviction ( key ) ) ;
167
174
168
175
return false ;
169
176
}
@@ -203,7 +210,7 @@ private TValue GetOrAddCore(TKey key, Func<TValue> valueFactory, TimeSpan ttl)
203
210
if ( ! wasAdded ) //performance hack: skip expiration check if a brand item was just added
204
211
{
205
212
if ( ttlValue . ModifyIfExpired ( valueFactory , ttl ) )
206
- OnEviction ( key ) ;
213
+ Task . Run ( ( ) => OnEviction ( key ) ) ;
207
214
}
208
215
209
216
return ttlValue . Value ;
@@ -288,6 +295,25 @@ private void OnEviction(TKey key)
288
295
} ) ;
289
296
}
290
297
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
+
291
317
private class TtlValue
292
318
{
293
319
public TValue Value { get ; private set ; }
0 commit comments