5using System.Threading.Tasks;
16 public class Cache<KeyType, ValueType> :
ICache, IDictionary<KeyType, ValueType>
18 private readonly Guid
id = Guid.NewGuid();
19 private readonly Dictionary<KeyType, CacheItem<KeyType, ValueType>> valuesByKey =
new Dictionary<KeyType, CacheItem<KeyType, ValueType>>();
20 private readonly SortedDictionary<DateTime, KeyType> keysByLastUsage =
new SortedDictionary<DateTime, KeyType>();
21 private readonly SortedDictionary<DateTime, KeyType> keysByCreation =
new SortedDictionary<DateTime, KeyType>();
22 private readonly SortedDictionary<DateTime, KeyType> keysByExpiry =
new SortedDictionary<DateTime, KeyType>();
23 private readonly Random rnd =
new Random();
24 private readonly
object synchObject =
new object();
25 private readonly
int maxItems;
26 private readonly
bool standalone;
27 private TimeSpan maxTimeUsed;
28 private TimeSpan maxTimeUnused;
30 private int maxTimerIntervalMs = 5000;
31 private int minTimerIntervalMs = 100;
32 private bool hasExplicitExpiry =
false;
60 Caches.Register(this.
id,
this);
63 private void CreateTimerLocked()
65 if (this.maxTimeUsed < TimeSpan.MaxValue ||
66 this.maxTimeUnused < TimeSpan.MaxValue)
68 int Interval = Math.Min((
int)((this.maxTimeUnused.TotalMilliseconds / 2) + 0.5),
this.maxTimerIntervalMs);
69 if (Interval < this.minTimerIntervalMs)
70 Interval = this.minTimerIntervalMs;
72 this.timer =
new Timer(this.TimerCallback,
null, Interval, Interval);
83 get => this.minTimerIntervalMs;
84 set => this.minTimerIntervalMs = value;
92 get => this.maxTimerIntervalMs;
93 set => this.maxTimerIntervalMs = value;
101 Caches.Unregister(this.
id);
111 private void TimerCallback(
object state)
116 DateTime UtcNow = DateTime.UtcNow;
122 lock (this.synchObject)
124 if (this.maxTimeUnused < TimeSpan.MaxValue)
126 Limit = UtcNow - this.maxTimeUnused;
128 foreach (KeyValuePair<DateTime, KeyType> P
in this.keysByLastUsage)
133 if (ToRemoveNotUsed is
null)
136 ToRemoveNotUsed.
Add(this.valuesByKey[P.Value]);
139 if (!(ToRemoveNotUsed is
null))
142 CacheItem<KeyType, ValueType> Item;
144 while (!(Loop is
null))
146 for (
int i = Loop.
Start, c = Loop.
Pos; i < c; i++)
150 this.valuesByKey.Remove(Item.Key);
151 this.keysByCreation.Remove(Item.Created);
152 this.keysByLastUsage.Remove(Item.LastUsed);
154 if (Item.Expires.HasValue &&
155 this.keysByExpiry.Remove(Item.Expires.Value))
157 this.hasExplicitExpiry = this.keysByExpiry.Count > 0;
168 if (this.maxTimeUsed < TimeSpan.MaxValue)
170 Limit = UtcNow - this.maxTimeUsed;
172 foreach (KeyValuePair<DateTime, KeyType> P
in this.keysByCreation)
177 if (ToRemoveOld is
null)
180 ToRemoveOld.
Add(this.valuesByKey[P.Value]);
183 if (!(ToRemoveOld is
null))
186 CacheItem<KeyType, ValueType> Item;
188 while (!(Loop is
null))
190 for (
int i = Loop.
Start, c = Loop.
Pos; i < c; i++)
194 this.valuesByKey.Remove(Item.Key);
195 this.keysByCreation.Remove(Item.Created);
196 this.keysByLastUsage.Remove(Item.LastUsed);
198 if (Item.Expires.HasValue &&
199 this.keysByExpiry.Remove(Item.Expires.Value))
201 this.hasExplicitExpiry = this.keysByExpiry.Count > 0;
212 if (this.hasExplicitExpiry)
214 foreach (KeyValuePair<DateTime, KeyType> P
in this.keysByExpiry)
219 if (ToRemoveExpired is
null)
222 ToRemoveExpired.
Add(this.valuesByKey[P.Value]);
225 if (!(ToRemoveExpired is
null))
228 CacheItem<KeyType, ValueType> Item;
230 while (!(Loop is
null))
232 for (
int i = Loop.
Start, c = Loop.
Pos; i < c; i++)
236 this.valuesByKey.Remove(Item.Key);
237 this.keysByCreation.Remove(Item.Created);
238 this.keysByLastUsage.Remove(Item.LastUsed);
239 this.keysByExpiry.Remove(Item.Expires.Value);
245 this.hasExplicitExpiry = this.keysByExpiry.Count > 0;
250 if (
Removed && this.valuesByKey.Count == 0)
252 this.timer?.Dispose();
257 if (!(ToRemoveNotUsed is
null))
260 if (!(ToRemoveOld is
null))
263 if (!(ToRemoveExpired is
null))
282 get => this.maxTimeUsed;
283 set => this.maxTimeUsed = value;
291 get => this.maxTimeUnused;
292 set => this.maxTimeUnused = value;
313 lock (this.synchObject)
315 if (this.valuesByKey.TryGetValue(Key, out CacheItem<KeyType, ValueType> Item))
319 this.keysByLastUsage.Remove(Item.LastUsed);
320 Item.LastUsed = this.GetLastUsageTimeLocked();
321 this.keysByLastUsage[Item.LastUsed] = Key;
340 lock (this.synchObject)
342 return this.valuesByKey.Count;
370 lock (this.synchObject)
372 Result =
new KeyType[this.valuesByKey.Count];
373 this.valuesByKey.Keys.CopyTo(Result, 0);
388 lock (this.synchObject)
390 Result =
new ValueType[this.valuesByKey.Count];
392 foreach (CacheItem<KeyType, ValueType> Rec
in this.valuesByKey.Values)
393 Result[i++] = Rec.Value;
409 private DateTime GetLastUsageTimeLocked()
411 DateTime TP = DateTime.UtcNow;
413 while (this.keysByLastUsage.ContainsKey(TP))
414 TP = TP.AddTicks(this.rnd.Next(1, 10));
425 public ValueType
this[KeyType Key]
432 throw new ArgumentException(
"Value not found.", nameof(Key));
437 this.
Add(Key, value);
446 public void Add(KeyType Key, ValueType Value)
448 this.
Add(Key, Value,
null,
null);
457 public void Add(KeyType Key, ValueType Value, DateTime? Expires)
459 this.
Add(Key, Value, Expires,
null);
468 public void Add(KeyType Key, ValueType Value, TimeSpan? Expires)
470 this.
Add(Key, Value,
null, Expires);
480 private void Add(KeyType Key, ValueType Value, DateTime? Expires, TimeSpan? Expires2)
482 CacheItem<KeyType, ValueType> Prev;
483 CacheItem<KeyType, ValueType> Item;
486 lock (this.synchObject)
488 if (this.valuesByKey.TryGetValue(Key, out Prev))
490 this.valuesByKey.Remove(Key);
491 this.keysByCreation.Remove(Prev.Created);
492 this.keysByLastUsage.Remove(Prev.LastUsed);
494 if (Prev.Expires.HasValue &&
495 this.keysByExpiry.Remove(Prev.Expires.Value))
497 this.hasExplicitExpiry = this.keysByExpiry.Count > 0;
506 if (this.valuesByKey.Count >=
this.maxItems)
508 KeyType OldKey =
default;
511 foreach (KeyType Key2
in this.keysByLastUsage.Values)
520 Prev = this.valuesByKey[OldKey];
522 this.valuesByKey.Remove(OldKey);
523 this.keysByCreation.Remove(Prev.Created);
524 this.keysByLastUsage.Remove(Prev.LastUsed);
526 if (Prev.Expires.HasValue &&
527 this.keysByExpiry.Remove(Prev.Expires.Value))
529 this.hasExplicitExpiry = this.keysByExpiry.Count > 0;
539 DateTime TP = DateTime.UtcNow;
541 while (this.keysByCreation.ContainsKey(TP) ||
542 this.keysByLastUsage.ContainsKey(TP))
544 TP = TP.AddTicks(this.rnd.Next(1, 10));
547 if (Expires2.HasValue)
548 Expires = TP.Add(Expires2.Value);
550 if (Expires.HasValue)
552 if (Expires.Value.Kind != DateTimeKind.Utc)
553 Expires = Expires.Value.ToUniversalTime();
555 while (this.keysByExpiry.ContainsKey(Expires.Value))
556 Expires = Expires.Value.AddTicks(this.rnd.Next(1, 10));
559 Item =
new CacheItem<KeyType, ValueType>(Key, Value, TP, Expires);
561 this.valuesByKey[Key] = Item;
562 this.keysByCreation[TP] = Key;
563 this.keysByLastUsage[TP] = Key;
565 if (Expires.HasValue)
567 this.keysByExpiry[Expires.Value] = Key;
568 this.hasExplicitExpiry =
true;
571 if (this.timer is
null)
572 this.CreateTimerLocked();
576 _ = this.OnRemoved(Key, Prev.Value, Reason);
579 private async Task OnRemoved(KeyType Key, ValueType Value,
RemovedReason Reason)
583 EventHandlerAsync<CacheItemEventArgs<KeyType, ValueType>> h = this.
Removed;
586 await h.Raise(
this,
new CacheItemEventArgs<KeyType, ValueType>(Key, Value, Reason));
594 private async
void OnRemoved(IEnumerable<CacheItem<KeyType, ValueType>> Items,
RemovedReason Reason)
596 EventHandlerAsync<CacheItemEventArgs<KeyType, ValueType>> h = this.
Removed;
601 foreach (CacheItem<KeyType, ValueType> Item
in Items)
602 await h.Raise(
this,
new CacheItemEventArgs<KeyType, ValueType>(Item.Key, Item.Value, Reason));
618 if (this.RemoveNoEvent(Key, out CacheItem<KeyType, ValueType> Item))
635 if (this.RemoveNoEvent(Key, out CacheItem<KeyType, ValueType> Item))
644 private bool RemoveNoEvent(KeyType Key, out CacheItem<KeyType, ValueType> Item)
646 lock (this.synchObject)
648 if (!this.valuesByKey.TryGetValue(Key, out Item))
651 this.valuesByKey.Remove(Item.Key);
652 this.keysByCreation.Remove(Item.Created);
653 this.keysByLastUsage.Remove(Item.LastUsed);
655 if (Item.Expires.HasValue &&
656 this.keysByExpiry.Remove(Item.Expires.Value))
658 this.hasExplicitExpiry = this.keysByExpiry.Count > 0;
661 if (this.valuesByKey.Count == 0)
663 this.timer?.Dispose();
674 public event EventHandlerAsync<CacheItemEventArgs<KeyType, ValueType>>
Removed =
null;
681 CacheItem<KeyType, ValueType>[]
Values;
683 lock (this.synchObject)
685 Values =
new CacheItem<KeyType, ValueType>[this.valuesByKey.Count];
686 this.valuesByKey.Values.CopyTo(
Values, 0);
687 this.valuesByKey.Clear();
688 this.keysByLastUsage.Clear();
689 this.keysByCreation.Clear();
690 this.keysByExpiry.Clear();
691 this.hasExplicitExpiry =
false;
693 this.timer?.Dispose();
704 public void Add(KeyValuePair<KeyType, ValueType> item)
706 this.
Add(item.Key, item.Value);
714 public bool Contains(KeyValuePair<KeyType, ValueType> item)
716 return this.
TryGetValue(item.Key, out ValueType Value) && Value.Equals(item.Value);
724 public void CopyTo(KeyValuePair<KeyType, ValueType>[] array,
int arrayIndex)
726 lock (this.synchObject)
728 foreach (CacheItem<KeyType, ValueType> Item
in this.valuesByKey.Values)
729 array[arrayIndex++] =
new KeyValuePair<KeyType, ValueType>(Item.Key, Item.Value);
737 public KeyValuePair<KeyType, ValueType>[]
ToArray()
739 KeyValuePair<KeyType, ValueType>[] Result;
742 lock (this.synchObject)
744 Result =
new KeyValuePair<KeyType, ValueType>[this.valuesByKey.Count];
746 foreach (CacheItem<KeyType, ValueType> Item
in this.valuesByKey.Values)
747 Result[i++] =
new KeyValuePair<KeyType, ValueType>(Item.Key, Item.Value);
758 public bool Remove(KeyValuePair<KeyType, ValueType> Item)
760 if (this.
TryGetValue(Item.Key, out ValueType Value) && Value.Equals(Item.Value))
761 return this.
Remove(Item.Key);
772 IEnumerable<KeyValuePair<KeyType, ValueType>> Array = this.
ToArray();
773 return Array.GetEnumerator();
780 IEnumerator IEnumerable.GetEnumerator()
782 return this.
ToArray().GetEnumerator();
Static class managing the application event log. Applications and services log events on this static ...
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.
Implements an in-memory cache.
ValueType[] GetValues()
Gets all available values in the cache.
bool ContainsKey(KeyType Key)
Checks if a key is available in the cache.
EventHandlerAsync< CacheItemEventArgs< KeyType, ValueType > > Removed
Event raised when an item has been removed from the cache.
KeyValuePair< KeyType, ValueType >[] ToArray()
Returns the contents of the cache as an array.
Cache(int MaxItems, TimeSpan MaxTimeUsed, TimeSpan MaxTimeUnused)
Implements an in-memory cache.
bool Remove(KeyValuePair< KeyType, ValueType > Item)
Removes an item from the cache.
void Dispose()
IDisposable.Dispose
bool Standalone
If cache is a standalone cache, or if it can be managed collectively with other caches.
int Count
Number of items in cache
int MaxTimerIntervalMs
Maximum expiry timer interval, in milliseconds.
int MinTimerIntervalMs
Minimum expiry timer interval, in milliseconds.
void Add(KeyType Key, ValueType Value, DateTime? Expires)
Adds an item to the cache.
ICollection< ValueType > Values
Values in cache.
bool IsReadOnly
If the dictionary is read-only.
void Add(KeyType Key, ValueType Value, TimeSpan? Expires)
Adds an item to the cache.
TimeSpan MaxTimeUsed
Maximum time to keep items that are being used.
bool Ping(KeyType Key)
Pings an entry in the cache, to keep it from being removed.
bool Remove(KeyType Key)
Removes an item from the cache.
async Task< bool > RemoveAsync(KeyType Key)
Removes an item from the cache. Waits for the removal event to complete before returning.
bool TryGetValue(KeyType Key, out ValueType Value)
Tries to get a value from the cache.
int MaxItems
Maximum number of items in cache.
void CopyTo(KeyValuePair< KeyType, ValueType >[] array, int arrayIndex)
Copies all items in the cache to an array.
void Add(KeyValuePair< KeyType, ValueType > item)
Adds an item to the cache.
IEnumerator< KeyValuePair< KeyType, ValueType > > GetEnumerator()
Gets an enumerator of contents in the cache.
KeyType[] GetKeys()
Gets all available keys in the cache.
ICollection< KeyType > Keys
Keys in cache.
void Add(KeyType Key, ValueType Value)
Adds an item to the cache.
void Clear()
Clears the cache.
Cache(int MaxItems, TimeSpan MaxTimeUsed, TimeSpan MaxTimeUnused, bool Standalone)
Implements an in-memory cache.
bool Contains(KeyValuePair< KeyType, ValueType > item)
Checks if an item (key and value) exists in the cache.
TimeSpan MaxTimeUnused
Maximum time to keep items that are not being used.
Repository of all active caches.
Node referencing a chunk in a ChunkedList<T>
ChunkNode< T > Next
Next chunk
int Pos
Index after the last element in chunk.
int Start
Index of first element in chunk.
A chunked list is a linked list of chunks of objects of type T .
ChunkNode< T > FirstChunk
First chunk
void Add(T Item)
Adds an item to the collection.
RemovedReason
Reason for removing the item.