Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
Cache.cs
1using System;
2using System.Collections;
3using System.Collections.Generic;
4using System.Threading;
5using Waher.Events;
6
8{
14 public class Cache<KeyType, ValueType> : ICache, IDictionary<KeyType, ValueType>
15 {
16 private readonly Guid id = Guid.NewGuid();
17 private readonly Dictionary<KeyType, CacheItem<KeyType, ValueType>> valuesByKey = new Dictionary<KeyType, CacheItem<KeyType, ValueType>>();
18 private readonly SortedDictionary<DateTime, KeyType> keysByLastUsage = new SortedDictionary<DateTime, KeyType>();
19 private readonly SortedDictionary<DateTime, KeyType> keysByCreation = new SortedDictionary<DateTime, KeyType>();
20 private readonly Random rnd = new Random();
21 private readonly object synchObject = new object();
22 private readonly int maxItems;
23 private readonly bool standalone;
24 private TimeSpan maxTimeUsed;
25 private TimeSpan maxTimeUnused;
26 private Timer timer;
27
34 public Cache(int MaxItems, TimeSpan MaxTimeUsed, TimeSpan MaxTimeUnused)
35 : this(MaxItems, MaxTimeUsed, MaxTimeUnused, false)
36 {
37 }
38
47 public Cache(int MaxItems, TimeSpan MaxTimeUsed, TimeSpan MaxTimeUnused, bool Standalone)
48 {
49 this.maxItems = MaxItems;
50 this.maxTimeUsed = MaxTimeUsed;
51 this.maxTimeUnused = MaxTimeUnused;
52 this.standalone = Standalone;
53
54 Caches.Register(this.id, this);
55 }
56
57 private void CreateTimerLocked()
58 {
59 if (this.maxTimeUsed < TimeSpan.MaxValue || this.maxTimeUnused < TimeSpan.MaxValue)
60 {
61 int Interval = Math.Min((int)((this.maxTimeUnused.TotalMilliseconds / 2) + 0.5), 5000);
62 if (Interval < 100)
63 Interval = 100;
64
65 this.timer = new Timer(this.TimerCallback, null, Interval, Interval);
66 }
67 else
68 this.timer = null;
69 }
70
74 public void Dispose()
75 {
76 Caches.Unregister(this.id);
77 this.Clear();
78 }
79
84 public bool Standalone => this.standalone;
85
86 private void TimerCallback(object state)
87 {
88 LinkedList<CacheItem<KeyType, ValueType>> ToRemove1 = null;
89 LinkedList<CacheItem<KeyType, ValueType>> ToRemove2 = null;
90 DateTime Now = DateTime.Now;
91 DateTime Limit;
92
93 try
94 {
95 lock (this.synchObject)
96 {
97 if (this.maxTimeUnused < TimeSpan.MaxValue)
98 {
99 Limit = Now - this.maxTimeUnused;
100
101 foreach (KeyValuePair<DateTime, KeyType> P in this.keysByLastUsage)
102 {
103 if (P.Key > Limit)
104 break;
105
106 if (ToRemove1 is null)
107 ToRemove1 = new LinkedList<CacheItem<KeyType, ValueType>>();
108
109 ToRemove1.AddLast(this.valuesByKey[P.Value]);
110 }
111
112 if (!(ToRemove1 is null))
113 {
114 foreach (CacheItem<KeyType, ValueType> Item in ToRemove1)
115 {
116 this.valuesByKey.Remove(Item.Key);
117 this.keysByCreation.Remove(Item.Created);
118 this.keysByLastUsage.Remove(Item.LastUsed);
119 }
120
121 if (this.valuesByKey.Count == 0)
122 {
123 this.timer?.Dispose();
124 this.timer = null;
125 }
126 }
127 }
128
129 if (this.maxTimeUsed < TimeSpan.MaxValue)
130 {
131 Limit = Now - this.maxTimeUsed;
132
133 foreach (KeyValuePair<DateTime, KeyType> P in this.keysByCreation)
134 {
135 if (P.Key > Limit)
136 break;
137
138 if (ToRemove2 is null)
139 ToRemove2 = new LinkedList<CacheItem<KeyType, ValueType>>();
140
141 ToRemove2.AddLast(this.valuesByKey[P.Value]);
142 }
143
144 if (!(ToRemove2 is null))
145 {
146 foreach (CacheItem<KeyType, ValueType> Item in ToRemove2)
147 {
148 this.valuesByKey.Remove(Item.Key);
149 this.keysByCreation.Remove(Item.Created);
150 this.keysByLastUsage.Remove(Item.LastUsed);
151 }
152
153 if (this.valuesByKey.Count == 0)
154 {
155 this.timer?.Dispose();
156 this.timer = null;
157 }
158 }
159 }
160 }
161
162 if (!(ToRemove1 is null))
163 this.OnRemoved(ToRemove1, RemovedReason.NotUsed);
164
165 if (!(ToRemove2 is null))
166 this.OnRemoved(ToRemove2, RemovedReason.Old);
167 }
168 catch (Exception ex)
169 {
170 Log.Exception(ex);
171 }
172 }
173
177 public int MaxItems => this.maxItems;
178
182 public TimeSpan MaxTimeUsed
183 {
184 get => this.maxTimeUsed;
185 set => this.maxTimeUsed = value;
186 }
187
191 public TimeSpan MaxTimeUnused
192 {
193 get => this.maxTimeUnused;
194 set => this.maxTimeUnused = value;
195 }
196
203 public bool TryGetValue(KeyType Key, out ValueType Value)
204 {
205 lock (this.synchObject)
206 {
207 if (this.valuesByKey.TryGetValue(Key, out CacheItem<KeyType, ValueType> Item))
208 {
209 Value = Item.Value;
210
211 this.keysByLastUsage.Remove(Item.LastUsed);
212 Item.LastUsed = this.GetLastUsageTimeLocked();
213 this.keysByLastUsage[Item.LastUsed] = Key;
214
215 return true;
216 }
217 else
218 {
219 Value = default;
220 return false;
221 }
222 }
223 }
224
228 public int Count
229 {
230 get
231 {
232 lock (this.synchObject)
233 {
234 return this.valuesByKey.Count;
235 }
236 }
237 }
238
242 public ICollection<KeyType> Keys => this.GetKeys();
243
247 public ICollection<ValueType> Values => this.GetValues();
248
252 public bool IsReadOnly => false;
253
258 public KeyType[] GetKeys()
259 {
260 KeyType[] Result;
261
262 lock (this.synchObject)
263 {
264 Result = new KeyType[this.valuesByKey.Count];
265 this.valuesByKey.Keys.CopyTo(Result, 0);
266 }
267
268 return Result;
269 }
270
275 public ValueType[] GetValues()
276 {
277 ValueType[] Result;
278 int i = 0;
279
280 lock (this.synchObject)
281 {
282 Result = new ValueType[this.valuesByKey.Count];
283
284 foreach (CacheItem<KeyType, ValueType> Rec in this.valuesByKey.Values)
285 Result[i++] = Rec.Value;
286 }
287
288 return Result;
289 }
290
296 public bool ContainsKey(KeyType Key)
297 {
298 return (this.TryGetValue(Key, out ValueType _));
299 }
300
301 private DateTime GetLastUsageTimeLocked()
302 {
303 DateTime TP = DateTime.Now;
304
305 while (this.keysByLastUsage.ContainsKey(TP))
306 TP = TP.AddTicks(this.rnd.Next(1, 10));
307
308 return TP;
309 }
310
317 public ValueType this[KeyType Key]
318 {
319 get
320 {
321 if (this.TryGetValue(Key, out ValueType Result))
322 return Result;
323 else
324 throw new ArgumentException("Value not found.", nameof(Key));
325 }
326
327 set
328 {
329 this.Add(Key, value);
330 }
331 }
332
338 public void Add(KeyType Key, ValueType Value)
339 {
340 CacheItem<KeyType, ValueType> Prev;
341 CacheItem<KeyType, ValueType> Item;
342 RemovedReason Reason;
343
344 lock (this.synchObject)
345 {
346 if (this.valuesByKey.TryGetValue(Key, out Prev))
347 {
348 this.valuesByKey.Remove(Key);
349 this.keysByCreation.Remove(Prev.Created);
350 this.keysByLastUsage.Remove(Prev.LastUsed);
351 Reason = RemovedReason.Replaced;
352 }
353 else
354 {
355 Reason = RemovedReason.Space;
356
357 if (this.valuesByKey.Count >= this.maxItems)
358 {
359 KeyType OldKey = default;
360 bool Found = false;
361
362 foreach (KeyType Key2 in this.keysByLastUsage.Values)
363 {
364 OldKey = Key2;
365 Found = true;
366 break;
367 }
368
369 if (Found)
370 {
371 Prev = this.valuesByKey[OldKey];
372
373 this.valuesByKey.Remove(OldKey);
374 this.keysByCreation.Remove(Prev.Created);
375 this.keysByLastUsage.Remove(Prev.LastUsed);
376 }
377 else
378 Prev = null;
379 }
380 else
381 Prev = null;
382 }
383
384 DateTime TP = DateTime.Now;
385
386 while (this.keysByCreation.ContainsKey(TP) || this.keysByLastUsage.ContainsKey(TP))
387 TP = TP.AddTicks(this.rnd.Next(1, 10));
388
389 Item = new CacheItem<KeyType, ValueType>(Key, Value, TP);
390
391 this.valuesByKey[Key] = Item;
392 this.keysByCreation[TP] = Key;
393 this.keysByLastUsage[TP] = Key;
394
395 if (this.timer is null)
396 this.CreateTimerLocked();
397 }
398
399 if (!(Prev is null))
400 this.OnRemoved(Key, Prev.Value, Reason);
401 }
402
403 private async void OnRemoved(KeyType Key, ValueType Value, RemovedReason Reason)
404 {
406
407 if (!(h is null))
408 {
409 try
410 {
411 await h(this, new CacheItemEventArgs<KeyType, ValueType>(Key, Value, Reason));
412 }
413 catch (Exception ex)
414 {
415 Log.Exception(ex);
416 }
417 }
418 }
419
420 private async void OnRemoved(IEnumerable<CacheItem<KeyType, ValueType>> Items, RemovedReason Reason)
421 {
423 try
424 {
425 if (!(h is null))
426 {
427 foreach (CacheItem<KeyType, ValueType> Item in Items)
428 {
429 try
430 {
431 await h(this, new CacheItemEventArgs<KeyType, ValueType>(Item.Key, Item.Value, Reason));
432 }
433 catch (Exception ex)
434 {
435 Log.Exception(ex);
436 }
437 }
438 }
439 }
440 catch (Exception ex)
441 {
442 Log.Exception(ex);
443 }
444 }
445
451 public bool Remove(KeyType Key)
452 {
453 CacheItem<KeyType, ValueType> Item;
454
455 lock (this.synchObject)
456 {
457 if (!this.valuesByKey.TryGetValue(Key, out Item))
458 return false;
459
460 this.valuesByKey.Remove(Item.Key);
461 this.keysByCreation.Remove(Item.Created);
462 this.keysByLastUsage.Remove(Item.LastUsed);
463
464 if (this.valuesByKey.Count == 0)
465 {
466 this.timer?.Dispose();
467 this.timer = null;
468 }
469 }
470
471 this.OnRemoved(Key, Item.Value, RemovedReason.Manual);
472
473 return true;
474 }
475
480
484 public void Clear()
485 {
486 CacheItem<KeyType, ValueType>[] Values;
487
488 lock (this.synchObject)
489 {
490 Values = new CacheItem<KeyType, ValueType>[this.valuesByKey.Count];
491 this.valuesByKey.Values.CopyTo(Values, 0);
492 this.valuesByKey.Clear();
493 this.keysByLastUsage.Clear();
494 this.keysByCreation.Clear();
495
496 this.timer?.Dispose();
497 this.timer = null;
498 }
499
500 this.OnRemoved(Values, RemovedReason.Manual);
501 }
502
507 public void Add(KeyValuePair<KeyType, ValueType> item)
508 {
509 this.Add(item.Key, item.Value);
510 }
511
517 public bool Contains(KeyValuePair<KeyType, ValueType> item)
518 {
519 return this.TryGetValue(item.Key, out ValueType Value) && Value.Equals(item.Value);
520 }
521
527 public void CopyTo(KeyValuePair<KeyType, ValueType>[] array, int arrayIndex)
528 {
529 lock (this.synchObject)
530 {
531 foreach (CacheItem<KeyType, ValueType> Item in this.valuesByKey.Values)
532 array[arrayIndex++] = new KeyValuePair<KeyType, ValueType>(Item.Key, Item.Value);
533 }
534 }
535
540 public KeyValuePair<KeyType, ValueType>[] ToArray()
541 {
542 KeyValuePair<KeyType, ValueType>[] Result;
543 int i = 0;
544
545 lock (this.synchObject)
546 {
547 Result = new KeyValuePair<KeyType, ValueType>[this.valuesByKey.Count];
548
549 foreach (CacheItem<KeyType, ValueType> Item in this.valuesByKey.Values)
550 Result[i++] = new KeyValuePair<KeyType, ValueType>(Item.Key, Item.Value);
551 }
552
553 return Result;
554 }
555
561 public bool Remove(KeyValuePair<KeyType, ValueType> item)
562 {
563 if (this.TryGetValue(item.Key, out ValueType Value) && Value.Equals(item.Value))
564 return this.Remove(item.Key);
565 else
566 return false;
567 }
568
573 public IEnumerator<KeyValuePair<KeyType, ValueType>> GetEnumerator()
574 {
575 IEnumerable<KeyValuePair<KeyType, ValueType>> Array = this.ToArray();
576 return Array.GetEnumerator();
577 }
578
583 IEnumerator IEnumerable.GetEnumerator()
584 {
585 return this.ToArray().GetEnumerator();
586 }
587 }
588}
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:13
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1647
Implements an in-memory cache.
Definition: Cache.cs:15
ValueType[] GetValues()
Gets all available values in the cache.
Definition: Cache.cs:275
bool ContainsKey(KeyType Key)
Checks if a key is available in the cache.
Definition: Cache.cs:296
KeyValuePair< KeyType, ValueType >[] ToArray()
Returns the contents of the cache as an array.
Definition: Cache.cs:540
Cache(int MaxItems, TimeSpan MaxTimeUsed, TimeSpan MaxTimeUnused)
Implements an in-memory cache.
Definition: Cache.cs:34
void Dispose()
IDisposable.Dispose
Definition: Cache.cs:74
bool Standalone
If cache is a standalone cache, or if it can be managed collectively with other caches.
Definition: Cache.cs:84
int Count
Number of items in cache
Definition: Cache.cs:229
bool Remove(KeyValuePair< KeyType, ValueType > item)
Removes an item from the cache.
Definition: Cache.cs:561
ICollection< ValueType > Values
Values in cache.
Definition: Cache.cs:247
bool IsReadOnly
If the dictionary is read-only.
Definition: Cache.cs:252
TimeSpan MaxTimeUsed
Maximum time to keep items that are being used.
Definition: Cache.cs:183
bool Remove(KeyType Key)
Removes an item from the cache.
Definition: Cache.cs:451
CacheItemEventHandler< KeyType, ValueType > Removed
Event raised when an item has been removed from the cache.
Definition: Cache.cs:479
bool TryGetValue(KeyType Key, out ValueType Value)
Tries to get a value from the cache.
Definition: Cache.cs:203
int MaxItems
Maximum number of items in cache.
Definition: Cache.cs:177
void CopyTo(KeyValuePair< KeyType, ValueType >[] array, int arrayIndex)
Copies all items in the cache to an array.
Definition: Cache.cs:527
void Add(KeyValuePair< KeyType, ValueType > item)
Adds an item to the cache.
Definition: Cache.cs:507
IEnumerator< KeyValuePair< KeyType, ValueType > > GetEnumerator()
Gets an enumerator of contents in the cache.
Definition: Cache.cs:573
KeyType[] GetKeys()
Gets all available keys in the cache.
Definition: Cache.cs:258
ICollection< KeyType > Keys
Keys in cache.
Definition: Cache.cs:242
void Add(KeyType Key, ValueType Value)
Adds an item to the cache.
Definition: Cache.cs:338
void Clear()
Clears the cache.
Definition: Cache.cs:484
Cache(int MaxItems, TimeSpan MaxTimeUsed, TimeSpan MaxTimeUnused, bool Standalone)
Implements an in-memory cache.
Definition: Cache.cs:47
bool Contains(KeyValuePair< KeyType, ValueType > item)
Checks if an item (key and value) exists in the cache.
Definition: Cache.cs:517
TimeSpan MaxTimeUnused
Maximum time to keep items that are not being used.
Definition: Cache.cs:192
Event arguments for cache item removal events.
Repository of all active caches.
Definition: Caches.cs:11
Interface for caches.
Definition: ICache.cs:9
delegate Task CacheItemEventHandler< KeyType, ValueType >(object Sender, CacheItemEventArgs< KeyType, ValueType > e)
Delegate for cache item removal event handlers.
RemovedReason
Reason for removing the item.