tsJensen

A quest for software excellence...

C# Thread Safe Class Essentials

In this post I'm recording my own code sample for future reference. It is not meant to be perfectly usable or even a representation of best practices but a practical reminder note of the most common thread safe code I end up writing. The code is a thread safe singleton class that demonstrates the use of the lock keyword, the Interlocked class, ReadWriterLockSlim class, and one of the new concurrent collections available in .NET 4.0.

In the code below, I've only shown the ConcurrentDictionary being used. There is a gold mine of power in the other concurrent types to be explored in other posts.

using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Threading;

namespace SafeThreads
{
  public sealed class DataCache
  {
    // Thread Safe Singleton Pattern
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++
    
    //volatile to insure assignment before read access
    private static volatile DataCache _instance;
    
    //the sync root object - clever name
    private static object _threadBolt = new object();

    private DataCache() { } //cannot create outside this

    public static DataCache Instance
    {
      get
      {
        if (null == _instance)
        {
          lock (_threadBolt)
          {
            if (null == _instance)
            {
              _instance = new DataCache();
            }
          }
        }
        return _instance;
      }
    }
    

    // Thread Safe with lock
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++

    private long _callCount = 0;
    private long _updateCount = 0;

    public long UpdateCountWithLock(long callCount)
    {
      lock (_threadBolt)
      {
        _callCount += callCount;
        _updateCount++;
        return _updateCount;
      }
    }

    // Thread Safe Interlocked class
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++

    public long UpdateCountWithInterlocked(long callCount)
    {
      Interlocked.Add(ref _callCount, callCount);
      Interlocked.Increment(ref _updateCount);
      return _updateCount;
    }

    private Tuple<int, string> _currentItem = 
      new Tuple<int, string>(0, string.Empty);

    public Tuple<int, string> CurrentItemInterlocked
    {
      get { return _currentItem; }
      set
      {
        Interlocked.Exchange<Tuple<int, string>>(ref _currentItem, value);
      }
    }


    // Thread Safe ReaderWriterLockSlim
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++

    private ReaderWriterLockSlim _slimLock = new ReaderWriterLockSlim();
    private Dictionary<string, int> _keys = new Dictionary<string, int>();
    private List<string> _values = new List<string>();

    public int Count
    {
      get
      {
        _slimLock.EnterReadLock();
        try
        {
          return _values.Count;
        }
        finally
        {
          _slimLock.ExitReadLock();
        }
      }
    }

    public string this[int index]
    {
      get
      {
        _slimLock.EnterReadLock();
        try
        {
          if (_values.Count > index && index > -1)
            return _values[index];
          else
            return null;
        }
        finally
        {
          _slimLock.ExitReadLock();
        }
      }
    }

    public string this[string key]
    {
      get
      {
        _slimLock.EnterReadLock();
        try
        {
          if (_keys.ContainsKey(key))
          {
            return _values[_keys[key]];
          }
          else
            return null;
        }
        finally
        {
          _slimLock.ExitReadLock();
        }
      }

      set
      {
        _slimLock.EnterWriteLock();
        try
        {
          if (_keys.ContainsKey(key))
          {
            _values[_keys[key]] = value;
          }
          else
          {
            _values.Add(value);
            _keys.Add(value, _values.Count - 1);
          }
        }
        finally
        {
          _slimLock.ExitWriteLock();
        }
      }
    }

    //demonstrate upgrade from read to write lock
    public AddUpdateResult AddOrUpdate(string key, string value)
    {
      AddUpdateResult result = AddUpdateResult.Unchanged;
      _slimLock.EnterUpgradeableReadLock();
      try
      {
        if (_keys.ContainsKey(key))
        {
          if (value == _values[_keys[key]])
            return result;
          else
            result = AddUpdateResult.Updated;
        }
        else
          result = AddUpdateResult.Added;

        //upgrade to write lock
        _slimLock.EnterWriteLock();
        try
        {
          if (result == AddUpdateResult.Updated)
          {
            _values[_keys[key]] = value;
          }
          else
          {
            _values.Add(value);
            _keys.Add(value, _values.Count - 1);
          }
        }
        finally
        {
          _slimLock.ExitWriteLock();
        }
        return result;
      }
      finally
      {
        _slimLock.ExitUpgradeableReadLock();
      }
    }

    public enum AddUpdateResult
    {
      Added,
      Updated,
      Unchanged
    };


    // Thread Safe Collections
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++

    private ConcurrentDictionary<string, string> _concurrentCache = 
      new ConcurrentDictionary<string, string>();

    public ConcurrentDictionary<string, string> ConcurrentCache
    {
      get { return _concurrentCache; }
    }
  }
}