A simple, thread-safe synchronized C# class to add, update, delete and read items from cache.
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
/// <summary>
/// Synchronized Cache (Thread Safe)
///
/// A simple, thread-safe synchronized class to add, update, delete and read
/// items from cache.
///
/// The cache holds strings with integer keys. An instance of
/// ReaderWriterLockSlim is used to synchronize access to the
/// Dictionary<TKey, TValue> that serves as the inner cache (_cache).
///
/// Modified version of SynchronizedCache example from MSDN:
/// https://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim(v=vs.110).aspx
/// </summary>
public class SynchronizedCache
{
/// <summary>
/// Uses the default constructor to create the lock, so recursion is
/// not allowed. Programming the ReaderWriterLockSlim is simpler
/// and less prone to error when the lock does not allow recursion.
/// </summary>
private ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
/// <summary>
/// The internal cache dictionary instance.
/// </summary>
private Dictionary<int, string> _cache = new Dictionary<int, string>();
/// <summary>
/// Returns a count of the total number of cached items.
/// </summary>
public int Count
{
get
{
_lock.EnterReadLock();
int count;
try
{
count = _cache.Count;
}
finally
{
_lock.ExitReadLock();
}
return count;
}
}
/// <summary>
/// Finds the cached item by key, and returns its value.
/// </summary>
/// <param name="key">The cache key.</param>
/// <returns>The cached item value.</returns>
public string Read(int key)
{
_lock.EnterReadLock();
string result;
try
{
result = _cache[key];
}
finally
{
_lock.ExitReadLock();
}
return result;
}
/// <summary>
/// Adds an item to be cached.
/// </summary>
/// <param name="key">The cache key.</param>
/// <param name="value">The cache value.</param>
public void Add(int key, string value)
{
_lock.EnterWriteLock();
try
{
_cache.Add(key, value);
}
finally
{
_lock.ExitWriteLock();
}
}
/// <summary>
/// Adds an item to be cached if it can do so within a specified timeout
/// period parameter.
/// </summary>
/// <param name="key">The cache key.</param>
/// <param name="value">The cache value.</param>
/// <param name="timeout">
/// The total time in milliseconds to wait for the Add operation to complete.
/// </param>
/// <returns>
/// Returns true if the cache item was inserted before the specified
/// timeout period, otherwise false.
/// </returns>
public bool Add(int key, string value, int timeout)
{
if (_lock.TryEnterWriteLock(timeout))
{
try
{
_cache.Add(key, value);
}
finally
{
_lock.ExitWriteLock();
}
return true;
}
else
{
return false;
}
}
/// <summary>
/// Adds or Updates a Cache Item
///
/// The operation will retrieve the value associated with a key and
/// compares it with a new value. If the value is unchanged, the method
/// returns a status indicating no change. If no value is found for
// the key, the key/value pair is inserted. If the value has changed,
// it is updated.
///
/// </summary>
/// <param name="key">The cache key.</param>
/// <param name="value">The cache value.</param>
/// <returns>The operation AddOrUpdateStatus.</returns>
public AddOrUpdateStatus AddOrUpdate(int key, string value)
{
// Upgradeable mode allows the thread to upgrade from read access to
// write access as needed, without the risk of deadlocks.
_lock.EnterUpgradeableReadLock();
try
{
string result = null;
if (_cache.TryGetValue(key, out result))
{
if (result == value)
{
return AddOrUpdateStatus.Unchanged;
}
else
{
_lock.EnterWriteLock();
try
{
_cache[key] = value;
}
finally
{
_lock.ExitWriteLock();
}
return AddOrUpdateStatus.Updated;
}
}
else
{
_lock.EnterWriteLock();
try
{
_cache.Add(key, value);
}
finally
{
_lock.ExitWriteLock();
}
return AddOrUpdateStatus.Added;
}
}
finally
{
_lock.ExitUpgradeableReadLock();
}
}
public void Delete(int key)
{
_lock.EnterWriteLock();
try
{
_cache.Remove(key);
}
finally
{
_lock.ExitWriteLock();
}
}
public enum AddOrUpdateStatus
{
Added,
Updated,
Unchanged
};
~SynchronizedCache()
{
if (_lock != null)
{
_lock.Dispose();
}
}
}
//
// ---------------------------------------------------------------------------
// EXAMPLE USAGE
// ---------------------------------------------------------------------------
//
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
public class Example
{
public static void Main()
{
var sc = new SynchronizedCache();
var tasks = new List<Task>();
int itemsWritten = 0;
// Execute a writer:
tasks.Add(Task.Run( () =>
{
string[] vegetables = {
"broccoli",
"cauliflower",
"carrot",
"sorrel",
"baby turnip",
"beet",
"brussel sprout",
"cabbage",
"plantain",
"spinach",
"grape leaves",
"lime leaves",
"corn",
"radish",
"cucumber",
"raddichio",
"lima beans"
};
for (int ctr = 1; ctr <= vegetables.Length; ctr++)
{
sc.Add(ctr, vegetables[ctr - 1]);
}
itemsWritten = vegetables.Length;
Console.WriteLine("Task {0} wrote {1} items\n", Task.CurrentId, itemsWritten);
}));
// Execute two readers, one to read from first to last
// and the second from last to first:
for (int ctr = 0; ctr <= 1; ctr++)
{
bool desc = Convert.ToBoolean(ctr);
tasks.Add(Task.Run( () =>
{
int start, last, step;
int items;
do
{
string output = string.Empty;
items = sc.Count;
if (! desc)
{
start = 1;
step = 1;
last = items;
}
else
{
start = items;
step = -1;
last = 1;
}
for (int index = start; desc ? index >= last : index <= last; index += step)
{
output += string.Format("[{0}] ", sc.Read(index));
}
Console.WriteLine("Task {0} read {1} items: {2}\n",
Task.CurrentId, items, output);
}
while (items < itemsWritten | itemsWritten == 0);
}));
}
// Execute a red/update task:
tasks.Add(Task.Run(() =>
{
Thread.Sleep(100);
for (int ctr = 1; ctr <= sc.Count; ctr++)
{
string value = sc.Read(ctr);
if (value == "cucumber")
{
if (sc.AddOrUpdate(ctr, "green bean")
!= SynchronizedCache.AddOrUpdateStatus.Unchanged)
{
Console.WriteLine("Changed 'cucumber' to 'green bean'");
}
}
}
}));
// Wait for all three tasks to complete:
Task.WaitAll(tasks.ToArray());
// Display the final contents of the cache:
Console.WriteLine();
Console.WriteLine("Values in synchronized cache: ");
for (int ctr = 1; ctr <= sc.Count; ctr++)
{
Console.WriteLine(" {0}: {1}", ctr, sc.Read(ctr));
}
Console.ReadKey();
}
}