Repository navigation
Expand file tree
/
Copy pathCachingWrapper.cs
More file actions
219 lines (187 loc) · 7.04 KB
/
Copy pathCachingWrapper.cs
File metadata and controls
219 lines (187 loc) · 7.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
using System;
using System.Collections.Generic;
using System.Threading;
namespace Utilities
{
/// <summary>
/// A thread safe wrapper around a db or service call which provides LRU Caching.
/// by dpan, 03/2009
/// </summary>
/// <typeparam name="TKey">The key type</typeparam>
/// <typeparam name="TValue">The value type</typeparam>
public class CachingWrapper<TKey, TValue> where TKey : IComparable
{
/// <summary>
/// The delegate method that will be called when a request cannot be fulfilled from the cache
/// </summary>
/// <param name="key">The Key</param>
/// <returns>The value</returns>
public delegate TValue RetrieveFromOriginalSource(TKey key);
#region Data Members
private readonly Dictionary<TKey, TValue> _localCache;
private readonly IndexedLinkedList<TKey> _lruList;
private readonly int _capacity;
private readonly RetrieveFromOriginalSource _originalSourceRetriever;
// The lockManager is used to provide Thread Safety when accessing our main dictionary object (_localCache)
private readonly ReaderWriterLockSlim _lockManager = new ReaderWriterLockSlim();
#endregion
/// <summary>
/// Initializes a new instance of the <see cref="CachingWrapper{TKey,TValue}"/> class.
/// </summary>
/// <param name="originalSourceRetriever">The method that will return an element if it is not in the cache.</param>
public CachingWrapper(RetrieveFromOriginalSource originalSourceRetriever)
: this(originalSourceRetriever, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CachingWrapper{TKey,TValue}"/> class.
/// </summary>
/// <param name="originalSourceRetriever">The method that will return an element if it is not in the cache.</param>
/// <param name="capacity">The maximum capacity of the cache. Use zero for infinite, non LRU capacity</param>
public CachingWrapper(RetrieveFromOriginalSource originalSourceRetriever, int capacity)
{
if (originalSourceRetriever == null)
{
throw new ArgumentException("originalSourceRetriever cannot be null");
}
_originalSourceRetriever += originalSourceRetriever;
_capacity = capacity;
if (capacity > 0)
{
_localCache = new Dictionary<TKey, TValue>(capacity);
_lruList = new IndexedLinkedList<TKey>();
}
else
{
_localCache = new Dictionary<TKey, TValue>();
}
}
/// <summary>
/// Retrieves the specified key.
/// </summary>
/// <param name="key">The key.</param>
/// <returns>The actual value</returns>
public TValue Retrieve(TKey key)
{
return Retrieve(key, Timeout.Infinite);
}
/// <summary>
/// Retrieves the specified key.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="forceFresh">if set to <c>true</c> forces a refresh of the cache from the source.</param>
/// <returns>The actual value</returns>
public TValue Retrieve(TKey key, bool forceFresh)
{
return forceFresh ? FetchFromOriginalSource(key, Timeout.Infinite) : Retrieve(key);
}
/// <summary>
/// Retrieves the specified key within a specified timeout
/// </summary>
/// <param name="key">The key</param>
/// <param name="lockTimeout">The lock timeout in milliseconds. The total time that the method is actually going to wait can be up to twice that amount.</param>
/// <returns>The actual value</returns>
public TValue Retrieve(TKey key, int lockTimeout)
{
if (_lockManager.TryEnterUpgradeableReadLock(lockTimeout))
{
try
{
TValue result;
if (!_localCache.TryGetValue(key, out result))
{
result = _originalSourceRetriever(key);
WriteToCache(key, result, lockTimeout);
}
PutKeyOnTop(key);
return result;
}
finally
{
_lockManager.ExitUpgradeableReadLock();
}
}
return FetchFromOriginalSource(key, lockTimeout);
}
/// <summary>
/// Clears the entire cache.
/// </summary>
public void ClearCache()
{
ClearCache(Timeout.Infinite);
}
/// <summary>
/// Clears the entire cache within the specified timeout.
/// </summary>
/// <param name="lockTimeout">The lock timeout in milliseconds.</param>
public bool ClearCache(int lockTimeout)
{
if (_lockManager.TryEnterWriteLock(lockTimeout))
{
try
{
_localCache.Clear();
_lruList.Clear();
return true;
}
finally
{
_lockManager.ExitWriteLock();
}
}
return false;
}
private TValue FetchFromOriginalSource(TKey key, int lockTimeout)
{
TValue result = _originalSourceRetriever(key);
if (WriteToCache(key, result, lockTimeout))
{
PutKeyOnTop(key);
}
return result;
}
private void PutKeyOnTop(TKey key)
{
if (_capacity > 0)
{
// Remove it from current position
_lruList.Remove(key);
// Add it again, this will result in it being placed on top
_lruList.Add(key);
}
}
private bool WriteToCache(TKey key, TValue value, int lockTimeout)
{
if (_lockManager.TryEnterWriteLock(lockTimeout))
{
try
{
if (_localCache.ContainsKey(key))
{
_localCache[key] = value; // refresh
PutKeyOnTop(key);
}
else
{
_localCache.Add(key, value);
if (_capacity > 0)
{
if (_localCache.Count > _capacity)
{
_localCache.Remove(_lruList.First);
_lruList.RemoveFirst();
}
_lruList.Add(key);
}
}
return true;
}
finally
{
_lockManager.ExitWriteLock();
}
}
return false;
}
}
}