1

I have a shared object between threads that is used to hold file state information. The object that holds the information is this class:

/// <summary>
/// A synchronized dictionary class.
/// Uses ReaderWriterLockSlim to handle locking. The dictionary does not allow recursion by enumeration. It is purly used for quick read access.
/// </summary>
/// <typeparam name="T">Type that is going to be kept.</typeparam>
public sealed class SynchronizedDictionary<U,T> : IEnumerable<T>
{
    private System.Threading.ReaderWriterLockSlim _lock = new System.Threading.ReaderWriterLockSlim();
    private Dictionary<U, T> _collection = null;

    public SynchronizedDictionary()
    {
        _collection = new Dictionary<U, T>();
    }

    /// <summary>
    /// if getting:
    /// Enters read lock.
    /// Tries to get the value.
    /// 
    /// if setting:
    /// Enters write lock.
    /// Tries to set value.
    /// </summary>
    /// <param name="key">The key to fetch the value with.</param>
    /// <returns>Object of T</returns>
    public T this[U key]
    { 
        get
        {
            _lock.EnterReadLock();
            try
            {
                return _collection[key];
            }
            finally
            {
                _lock.ExitReadLock();
            }
        }

        set
        {
            Add(key, value);
        }

    }

    /// <summary>
    /// Enters write lock. 
    /// Removes key from collection
    /// </summary>
    /// <param name="key">Key to remove.</param>
    public void Remove(U key)
    {
        _lock.EnterWriteLock();
        try
        {
            _collection.Remove(key);
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    /// <summary>
    /// Enters write lock.
    /// Adds value to the collection if key does not exists.
    /// </summary>
    /// <param name="key">Key to add.</param>
    /// <param name="value">Value to add.</param>
    private void Add(U key, T value)
    {
        _lock.EnterWriteLock();
        if (!_collection.ContainsKey(key))
        {
            try
            {
                _collection[key] = value;
            }
            finally
            {
                _lock.ExitWriteLock();
            }
        }

    }

    /// <summary>
    /// Collection does not support iteration.
    /// </summary>
    /// <returns>Throw NotSupportedException</returns>
    public IEnumerator<T> GetEnumerator()
    {
        throw new NotSupportedException();
    }

    /// <summary>
    /// Collection does not support iteration.
    /// </summary>
    /// <returns>Throw NotSupportedException</returns>
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        throw new NotSupportedException();
    }

}

I call this dictionary like this: SynchronizedDictionary _cache = new SynchronizedDictionary();

Other threads can be spawned and use the thread like this: _cache["key"];

The dictionary can be modified at runtime. I see no problem here. Or am I wrong? The problem, in my eyes, lies in the enumerator, because I want to make an enumerator that iterates over the collection. How do I do this? I have thought of these three solutions:

  1. Making a Enumerator like this: http://www.codeproject.com/Articles/56575/Thread-safe-enumeration-in-C (but using ReaderWriterLockSlim)
  2. Expose the lock object, like SyncRoot does (but with ReaderWriterLockSlim), so a caller calls the enter and exit read methods.
  3. Use a database (SQLite fx) instead, holding the information.

The problem with number 1) is:

  1. it uses the contructor to entry read mode. What if the GetEnumerator() is call manually, not using the foreach? And forget calling dispose.
  2. I do not know if this is a good coding style. Even though I like the code.
  3. If the caller uses a foreach, I do not know what the caller might do between the instantiation of the enumerator and the call to dispose. If I have understood the documentation I have read correct this can end up blocking the writer as long as there is one reader left doing some heavy work.

The problem with number 2) is:

  1. I do not like exposing this. I know that the .NET API does it, but do not like it.
  2. It is up to the caller to enter and exit properly

There is no problem with 3) I my eyes. But I am doing this small project as a spare time project and I want to learn more about multi-threading and reflection, so I want to keep this as a last option. The reason why I want to iterate over the collection at runtime is that I want to find the values, that matches some criteria.

Maybe it is just me that have invented a problem?

I know of ConcurrentDictionary, but I do not want to use this. I am using this project as a playground. Playing with threading and reflection.

EDIT

I have been asked what it is that I am reading and writing. And I am going to tell this in this edit. I am reading and writing this class:

public class AssemblyInformation
{
    public string FilePath { get; private set; }
    public string Name { get; private set; }

    public AssemblyInformation(string filePath, string name)
    {
        FilePath = filePath;
        Name = name;
    }
}

I am doing alot of reads, and almost no writes at runtime. Maybe I will do 2000 and 1 write. There is not going to be alot of object either, maybe 200.

mslot
  • 4,959
  • 9
  • 44
  • 76
  • Seems to be a nice question but too long to read – L.B Mar 24 '12 at 18:58
  • Sorry, but I do not know of a way telling it shorter. – mslot Mar 24 '12 at 19:01
  • I don't get the problem. Unless you somehow chose a gimpy key for the dictionary. This just isn't a problem if you use the full path of the DLL as the key. – Hans Passant Mar 24 '12 at 19:24
  • The problem is that the key is the command name. I have choosen this so looking up a command will be fast. – mslot Mar 24 '12 at 19:31
  • 1
    Maybe I haven't understood your problem, but have you tried using ConcurrentDictionary ? – Dmitry Reznik Mar 24 '12 at 20:18
  • @Dimitriy Reznik, I am aware of that dictionary, but I am trying to make my own. To see how this is done, and to learn something about threading. Basically I want to know if this, http://www.codeproject.com/Articles/56575/Thread-safe-enumeration-in-C is a good and _proper_ way of doing iteration over and collection of object. I want to take the code from the link and rewrite it so it uses ReaderWriterLockSlim. – mslot Mar 24 '12 at 21:54
  • I have tried to shorten the question. – mslot Mar 24 '12 at 22:28
  • This is completely orthogonal to your question, but your Add method will not release the WriteLock if the key already exists in the dictionary. Also if the key already exists, the setter will not change the value. That is just weird. – Mike Zboray Mar 25 '12 at 00:19
  • Haha thanks for info Mike:-) i do not know how I ended writing that :s – mslot Mar 25 '12 at 08:16

2 Answers2

2

I'll treat your questions as a request for feedback which helps you learn. Let me address the three solutions you have already identified:

  1. Yes, this is why such a design should never be exposed as an API to a 3rd-party (or even other developers). It is tricky to use correctly. This codeproject article has some nasty advice.
  2. Much better because this model would be explicit about locking, not implicit. However this violates separation of concerns in my opinion.
  3. Not sure what you mean here. You could have a Snapshot() method on your dictionary which does a read-only copy which can be safely passed around and read. This is a different trade-off than solution 1.

There is a different solution entirely: Use an immutable dictionary. Such a dictionary could be passed around, read and enumerated safely even under concurrent write access. Such dictionaries/maps are commonly implemented using trees.

I'll elaborate more on a key point: You need to think about the concurrent system as a whole. You cannot make you app correct by making all components thread-safe (in your case a dictionary). You need to define, what you are using the dictionary for.

You say:

The reason why I want to iterate over the collection at runtime is that I want to find the values, that matches some criteria.

You you have concurrent writes happening to the data and want to get a consistent snapshot atomically from the dictionary (maybe to shot some progress report in the UI?). Now that we know this goal, we can devise a solution:

You could add a Clone method to your dictionary which clones all data while taking the read-lock. This will give the caller a fresh object which it can then enumerate over independently. This would be a clean and safely exposable API.

usr
  • 168,620
  • 35
  • 240
  • 369
  • Yes, I want feedback :) I have updated my 3 question. But I see that you have somewhat understood it. I am more interested in hearing about your "different solution" though. Are you reffering to some sort of "ReadOnlyDictionary"? – mslot Mar 24 '12 at 22:51
  • I added more thought. What are you using the dictionary for? What are you reading and writing? – usr Mar 24 '12 at 23:01
  • I like yo way of thinking. I have edit my question and added some info of what it is I am reading and writing. It is not alot, and maybe somewhere in my mind I know that I could easily do a clone and read that. Maybe that is just the best solution because that would give me a valid footprint of the list at the current time. But I think that it is a consuming even though I am not working on alot of objects. – mslot Mar 25 '12 at 00:13
  • When you have that few write you could even treat the dictionary as read-only. Every time you write you create a new one (under a lock) and write it into the global variable the readers are reading from. Readers don't even need a read-lock that way. They just get the last valid version. – usr Mar 25 '12 at 11:13
  • I have accepted this, because of the excellent feedback. I have made a method that, takes the readlock and copy it. This is the safetst way. I can see that now. – mslot Mar 29 '12 at 19:55
2

Instead of implementing IEnumerable directly I would add a Values property (like Dictionary.Values):

public IEnumerable<T> Values {
  get {
    _lock.EnterReadLock();
    try {
      foreach (T v in _collection.Values) {   
        yield return v;
      }
    } finally {
      _lock.ExitReadLock();
    }
  }
}
MiMo
  • 11,793
  • 1
  • 33
  • 48
  • I have voted this on up. It was not what I was looking for because I need a way of finding the corrosponding key to the value, but I have used this afterwards in the collection. Beautiful way of using the yield. Thanks for your answer. – mslot Mar 29 '12 at 19:57
  • 1
    You're welcome. Of course with the same technique you can get back an enumeration of the key-value pairs. – MiMo Mar 30 '12 at 12:30