|
|
|
@ -18,107 +18,47 @@
@@ -18,107 +18,47 @@
|
|
|
|
|
|
|
|
|
|
using System; |
|
|
|
|
using System.Collections.Concurrent; |
|
|
|
|
using System.Collections.Generic; |
|
|
|
|
using System.Threading; |
|
|
|
|
|
|
|
|
|
namespace ICSharpCode.NRefactory.Utils |
|
|
|
|
{ |
|
|
|
|
/// <summary>
|
|
|
|
|
/// Allows the registration of static "caching types" which can then be used to efficiently retrieve an
|
|
|
|
|
/// instance per CacheManager (or even per CacheManager and thread).
|
|
|
|
|
/// Allows caching values for a specific resolve context.
|
|
|
|
|
/// A CacheManager consists of two dictionaries: one for shared instances (shared among all threads working with that resolve context),
|
|
|
|
|
/// and one for thread-local instances.
|
|
|
|
|
/// Additionally, it provides a Dispose() event that can be used to clear any external caches when
|
|
|
|
|
/// leaving the "using (var ctx = context.Synchronize())" block.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <remarks>This class is thread-safe</remarks>
|
|
|
|
|
public sealed class CacheManager : IDisposable |
|
|
|
|
{ |
|
|
|
|
readonly ConcurrentDictionary<object, object> dict = new ConcurrentDictionary<object, object>(ReferenceComparer.Instance); |
|
|
|
|
readonly ConcurrentDictionary<object, object> sharedDict = new ConcurrentDictionary<object, object>(ReferenceComparer.Instance); |
|
|
|
|
readonly ThreadLocal<Dictionary<object, object>> localDict = new ThreadLocal<Dictionary<object, object>>(() => new Dictionary<object, object>(ReferenceComparer.Instance)); |
|
|
|
|
|
|
|
|
|
public ConcurrentDictionary<object, object> Dictionary { |
|
|
|
|
get { return dict; } |
|
|
|
|
public object GetShared(object key) |
|
|
|
|
{ |
|
|
|
|
object val; |
|
|
|
|
sharedDict.TryGetValue(key, out val); |
|
|
|
|
return val; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Lots of code commented out because I don't know if it's useful, clients can usually replicate |
|
|
|
|
* the functionality much more easily and only need the Disposed event to ensure cleanup. |
|
|
|
|
* |
|
|
|
|
* Actually, what I've implemented here looks very much like .NET's internal System.LocalDataStore |
|
|
|
|
* (used for Thread.GetData/SetData) |
|
|
|
|
* |
|
|
|
|
static int nextSharedIndex, nextThreadLocalIndex; |
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Registers a new cache type. This causes each CacheManager to allocate space for the new cache type.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="isThreadLocal">Specifies whether this cache is shared (multi-threaded) or whether
|
|
|
|
|
/// there is one instance per thread.</param>
|
|
|
|
|
/// <returns>Returns a token that can be used to access the cache.</returns>
|
|
|
|
|
public static CacheToken<T> RegisterType<T>(CacheMode mode) where T : class, new() |
|
|
|
|
public void SetShared(object key, object val) |
|
|
|
|
{ |
|
|
|
|
int index; |
|
|
|
|
switch (mode) { |
|
|
|
|
case CacheMode.Shared: |
|
|
|
|
index = Interlocked.Increment(ref nextSharedIndex); |
|
|
|
|
break; |
|
|
|
|
case CacheMode.ThreadLocal: |
|
|
|
|
index = Interlocked.Increment(ref nextThreadLocalIndex); |
|
|
|
|
break; |
|
|
|
|
default: |
|
|
|
|
throw new ArgumentException("Invalid value for CacheMode", "mode"); |
|
|
|
|
} |
|
|
|
|
return new CacheToken<T>(mode, index); |
|
|
|
|
sharedDict[key] = val; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
readonly object lockObj = new object(); |
|
|
|
|
volatile object[] _sharedCaches = new object[nextSharedIndex]; |
|
|
|
|
ThreadLocal<object[]> threadLocalCaches = new ThreadLocal<object[]>(() => new object[nextThreadLocalIndex]); |
|
|
|
|
public object GetThreadLocal(object key) |
|
|
|
|
{ |
|
|
|
|
object val; |
|
|
|
|
localDict.Value.TryGetValue(key, out val); |
|
|
|
|
return val; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the cache using the specified token.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public T Get<T>(CacheToken<T> token) where T : class, new() |
|
|
|
|
public void SetThreadLocal(object key, object val) |
|
|
|
|
{ |
|
|
|
|
switch (token.Mode) { |
|
|
|
|
case CacheMode.Shared: |
|
|
|
|
object[] sharedCaches = this._sharedCaches; |
|
|
|
|
if (token.Index < sharedCaches.Length) { |
|
|
|
|
object c = sharedCaches[token.Index]; |
|
|
|
|
if (c != null) |
|
|
|
|
return (T)c; |
|
|
|
|
} |
|
|
|
|
// it seems like the cache doesn't exist yet, so try to create it:
|
|
|
|
|
T newCache = new T(); |
|
|
|
|
lock (lockObj) { |
|
|
|
|
sharedCaches = this._sharedCaches; // fetch fresh value after locking
|
|
|
|
|
// use double-checked locking
|
|
|
|
|
if (token.Index < sharedCaches.Length) { |
|
|
|
|
object c = sharedCaches[token.Index]; |
|
|
|
|
if (c != null) { |
|
|
|
|
// looks like someone else was faster creating it than this thread
|
|
|
|
|
return (T)c; |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
Array.Resize(ref sharedCaches, nextSharedIndex); |
|
|
|
|
this._sharedCaches = sharedCaches; |
|
|
|
|
} |
|
|
|
|
sharedCaches[token.Index] = newCache; |
|
|
|
|
} |
|
|
|
|
return newCache; |
|
|
|
|
case CacheMode.ThreadLocal: |
|
|
|
|
object[] localCaches = threadLocalCaches.Value; |
|
|
|
|
if (token.Index >= localCaches.Length) { |
|
|
|
|
Array.Resize(ref localCaches, nextThreadLocalIndex); |
|
|
|
|
threadLocalCaches.Value = localCaches; |
|
|
|
|
} |
|
|
|
|
object lc = localCaches[token.Index]; |
|
|
|
|
if (lc != null) { |
|
|
|
|
return (T)lc; |
|
|
|
|
} else { |
|
|
|
|
T newLocalCache = new T(); |
|
|
|
|
localCaches[token.Index] = newLocalCache; |
|
|
|
|
return newLocalCache; |
|
|
|
|
} |
|
|
|
|
default: |
|
|
|
|
throw new ArgumentException("Invalid token"); |
|
|
|
|
} |
|
|
|
|
localDict.Value[key] = val; |
|
|
|
|
} |
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
public event EventHandler Disposed; |
|
|
|
|
|
|
|
|
@ -127,7 +67,8 @@ namespace ICSharpCode.NRefactory.Utils
@@ -127,7 +67,8 @@ namespace ICSharpCode.NRefactory.Utils
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void Dispose() |
|
|
|
|
{ |
|
|
|
|
//threadLocalCaches.Dispose(); // dispose the ThreadLocal<T>
|
|
|
|
|
sharedDict.Clear(); |
|
|
|
|
localDict.Dispose(); // dispose the ThreadLocal<T>
|
|
|
|
|
// TODO: test whether this frees the referenced value on all threads
|
|
|
|
|
|
|
|
|
|
// fire Disposed() only once by removing the old event handlers
|
|
|
|
@ -136,24 +77,4 @@ namespace ICSharpCode.NRefactory.Utils
@@ -136,24 +77,4 @@ namespace ICSharpCode.NRefactory.Utils
|
|
|
|
|
disposed(this, EventArgs.Empty); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
public enum CacheMode |
|
|
|
|
{ |
|
|
|
|
// don't use 0 so that default(CacheToken<...>) is an invalid mode
|
|
|
|
|
Shared = 1, |
|
|
|
|
ThreadLocal = 2 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public struct CacheToken<T> where T : class, new() |
|
|
|
|
{ |
|
|
|
|
internal readonly CacheMode Mode; |
|
|
|
|
internal readonly int Index; |
|
|
|
|
|
|
|
|
|
internal CacheToken(CacheMode mode, int index) |
|
|
|
|
{ |
|
|
|
|
this.Mode = mode; |
|
|
|
|
this.Index = index; |
|
|
|
|
} |
|
|
|
|
}*/ |
|
|
|
|
} |
|
|
|
|