0

I have made an attempt at creating a ThreadSafeSortedDictionary, and I am working with .NET 4.0.

I have had a look at Thread safe SortedDictionary but not confident about the thread safety aspect.

Anyone think this can work, or NOT for a reason I cannot see associated with:

  1. Thread safety - it needs to be thread safe
  2. Performance/Efficiency - I am not majorly concerned about this (unless there is a huge problem with performance)

My code:

public class ThreadSafeSortedDict<TKey, TValue>
{
    private readonly SortedDictionary<TKey, TValue> _dict;
    private readonly ReaderWriterLockSlim _dictReaderWriterLockSlim;
    private readonly ReaderWriterLockSlim _readonlyDictionaryLock;

    public Dictionary<TKey, TValue> ReadOnly { get; set; }

    public ThreadSafeSortedDict(IComparer<TKey> comparer)
    {
        _dict = new SortedDictionary<TKey, TValue>(comparer);
        _dictReaderWriterLockSlim = new ReaderWriterLockSlim();
        _readonlyDictionaryLock = new ReaderWriterLockSlim();
    }

    public void Add(TKey key, TValue value)
    {
        _dictReaderWriterLockSlim.EnterWriteLock();
        try
        {
            _dict.Add(key,value);
        }
        finally
        {
            _dictReaderWriterLockSlim.ExitWriteLock();
        }

        SetReadOnlyDictionary();
    }

    public void AddRange(IEnumerable<KeyValuePair<TKey,TValue>> keyValues)
    {
        if (keyValues == null) return;
        _dictReaderWriterLockSlim.EnterWriteLock();
        try
        {
            foreach (var keyValue in keyValues)
            {
                Add(keyValue.Key, keyValue.Value);
            }
        }
        finally
        {
            _dictReaderWriterLockSlim.ExitWriteLock();
        }

        SetReadOnlyDictionary();
    }

    public void Remove(TKey key)
    {
        _dictReaderWriterLockSlim.EnterWriteLock();
        try
        {
            _dict.Remove(key);
        }
        finally
        {
            _dictReaderWriterLockSlim.ExitWriteLock();
        }

        SetReadOnlyDictionary();
    }

    public void Replace(IEnumerable<KeyValuePair<TKey, TValue>> newKeyValues)
    {
        if (newKeyValues == null) return;

        _dictReaderWriterLockSlim.EnterWriteLock();

        try
        {
            _dict.Clear();
            AddRange(newKeyValues);
        }
        finally
        {
            _dictReaderWriterLockSlim.ExitWriteLock();
        }
    }

    private void SetReadOnlyDictionary()
    {
        _readonlyDictionaryLock.EnterWriteLock();
        try
        {
            ReadOnly = GetSortedKeyValues().ToDictionary(x => x.Key, x => x.Value);
        }
        finally
        {
            _readonlyDictionaryLock.ExitWriteLock();
        }
    }

    private List<KeyValuePair<TKey, TValue>> GetSortedKeyValues()
    {
        _dictReaderWriterLockSlim.EnterReadLock();
        try
        {
            return _dict.ToList();
        }
        finally
        {
            _dictReaderWriterLockSlim.ExitReadLock();
        }
    }
}
Community
  • 1
  • 1
MajorInc
  • 342
  • 3
  • 12
  • if performance is not important , why not use a ConcurentDictonary and on GetSortedKeyValues do a sort of values? – Shachaf.Gortler Oct 11 '16 at 15:50
  • Ok I think i probably can replace the SortedDictionary with a ConcurrentDictionary. And then sort them when I set the ReadOnly dictionary. But then I would be sorting every time I add / remove something. But this will remove some code when adding/removing the values. – MajorInc Oct 11 '16 at 16:14
  • You dont need to sort on add or remove , you only have to sort when you need to export the data – Shachaf.Gortler Oct 11 '16 at 17:37
  • Ok so you mean the getter of the ReadOnly property. But its better to do it on the add/remove because the read happens alot whereas add/remove does not that often. – MajorInc Oct 11 '16 at 18:08
  • 2
    if that the scenario , then yes it makes since to put the sort in the add remove section – Shachaf.Gortler Oct 11 '16 at 18:35
  • 1
    So what's your question again? – VMAtm Oct 11 '16 at 22:00

2 Answers2

2

I don't see any obvious thread-safety issues with your code. To me, the bigger issues are matters of correctness:

  1. A minor problem is that your ReadOnly property has a public setter. This means any code outside the class can set the property at any time, without any synchronization. Your class design should reflect the intended usage. Presumably you don't want any other code to change the property value, so the setter should be private.
  2. A major problem is that while the class name is ThreadSafeSortedDict<TKey, TValue>, there's no publicly accessible view of the data that is sorted. When you set the ReadOnly property, you create a new unsorted dictionary from the original data. Even though you enumerate the source dictionary in order, which is sorted, there are no guarantees that the data in the new dictionary will remain in the order in which it was added. The dictionary class is, by design, an unordered collection.

If you really want to maintain the data in a SortedDictionary<TKey, TValue> object, and present a sorted view of it, you need to use some type of ordered collection as your ReadOnly property type. This could be another SortedDictionary<TKey, TValue>, but since the intent is for the collection to be read-only, IMHO it makes more sense to make the type IReadOnlyDictionary<TKey, TValue>, returning a ReadOnlyDictionary<TKey, TValue> object. And rather than creating a new object every time the sorted dictionary is modified, you can initialize it lazily and invalidate it when the sorted dictionary is modified.

For example:

public class ThreadSafeSortedDict<TKey, TValue>
{
    private readonly SortedDictionary<TKey, TValue> _dict;
    private IReadOnlyDictionary<TKey, TValue> _readOnly;
    private readonly object _lock = new object();

    public IReadOnlyDictionary<TKey, TValue> ReadOnly
    {
        get
        {
            lock (_lock)
            {
                if (_readOnly == null)
                {
                  // NOTE: SortedList is faster when populating from already-sorted data
                    _readOnly = new ReadOnlyDictionary(new SortedList(_dict));
                }
            }

            return _readOnly;
        }
    }

    public ThreadSafeSortedDict(IComparer<TKey> comparer)
    {
        _dict = new SortedDictionary<TKey, TValue>(comparer);
    }

    public void Add(TKey key, TValue value)
    {
        lock (_lock)
        {
            _dict.Add(key,value);
            _readOnly = null;
        }
    }

    public void AddRange(IEnumerable<KeyValuePair<TKey,TValue>> keyValues)
    {
        if (keyValues == null) return;
        lock (_lock)
        {
            foreach (var keyValue in keyValues)
            {
                Add(keyValue.Key, keyValue.Value);
            }
            _readOnly = null;
        }
    }

    public void Remove(TKey key)
    {
        lock (_lock)
        {
            _dict.Remove(key);
            _readOnly = null;
        }
    }

    public void Replace(IEnumerable<KeyValuePair<TKey, TValue>> newKeyValues)
    {
        if (newKeyValues == null) return;

        lock (_lock)
        {
            _dict.Clear();
            AddRange(newKeyValues);
            _readOnly = null;
        }
    }
}

Using lock instead of the reader-writer lock should not affect performance significantly, if at all, and makes the code a lot more obviously thread-safe. It seems to me that the above should be sufficient. Still, if it's not, some improvements you could make include:

  1. Going ahead and using the reader-writer lock. You'll need to acquire the reader lock in the ReadOnly property, and upgrade it to a writer lock if you need to set the _readOnly field (i.e. if it's set to null). This could improve performance if you have a very large amount of contention on the ReadOnly property.
  2. Not bothering with the custom type, and using ConcurrentDictionary<TKey, TValue> instead, just copying its contents and sorting them after the fact when you need a sorted view of the dictionary. Whether this is useful in your case I can't say, as there's not enough context in the question. If you will still need to implement some type of synchronization to avoid trying to retrieve the sorted view when some other thread is trying to modify the collection, then you'll probably want some kind of wrapper type anyway.
  3. Having your type implement IReadOnlyDictionary<TKey, TValue> rather than having a ReadOnly property. Then you can pass your object as a read-only dictionary object where needed, but code that needs to actually modify the dictionary can still do so via the original type.

Given what information is available in the question at the moment, I'd say option #3 is probably best. It would require the most work, but seems to me to provide the cleanest, most efficient API. You would avoid having to have the ReadOnly property and you would avoid having to make a copy of the collection. The class itself would serve as the read-only copy, and it would automatically be kept up-to-date with respect to the underlying dictionary collection, because the implementation of IReadOnlyDictionary<TKey, TValue> would just defer to the underlying dictionary (with synchronization, of course).

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • Tyvm Peter.As i am on .NET 4.0 - I do not have access to the ReadOnlyDictionary or the interface. So my best bet is to use the solution you wrote. – MajorInc Oct 14 '16 at 16:22
  • Peter, also regarding your other point that "GetSortedKeyValues().ToDictionary(x => x.Key, x => x.Value)" does not guarantee that the result Dictionary will be sorted. Are you saying that passing in a SortedList to the constructor of the Dictionary: "Dictionary(new SortedList(_dict))" will guarantee that the new Dictionary will be in the order of the underlying "_dict" which is a SortedDictionary respecting its "comparer". – MajorInc Oct 14 '16 at 16:25
  • Actually I think i know the answer to ^^. Instead I need to pass in the comparer if the _dict aswell: "ReadOnly = new Dictionary(new SortedList(_dict, _dict.Comparer))" – MajorInc Oct 14 '16 at 16:48
  • When assigning ReadOnly using the ToDictionary on the underlying sorted dictionary, the ReadOnly dictionary is indeed sorted at that point in time. But I agree that ReadOnly dictionary will not remain sorted when new Data which is Added to it, and the new added data will be added in an unsorted manner. (But in my context the Data should not be added to the ReadOnly dictionary, so I think I would be fine) – MajorInc Oct 14 '16 at 17:07
  • _"Are you saying that passing in a SortedList to the constructor of the Dictionary: "Dictionary(new SortedList(_dict))" will guarantee that the new Dictionary will be in the order of the underlying "_dict""_ -- no, quite the opposite. Whether you use `ToDictionary()` or you pass an `IDictionary` object to the constructor, the _new_ dictionary will be unordered. It _might_ wind up with elements the same order as the original, but this is not guaranteed, and if the dictionary is modified, could change. You cannot assume any dictionary type other than the `Sorted...` types are ordered. – Peter Duniho Oct 14 '16 at 17:17
  • _"the ReadOnly dictionary is indeed sorted at that point in time"_ -- **No.** If you found it sorted, that was just luck. You are more likely to be lucky the smaller the dictionary, but there simply is not any way to guarantee it will always wind up sorted, even in a brand new instance of the dictionary. – Peter Duniho Oct 14 '16 at 17:18
  • Ah ok, I assumed the reason you initially used SortedList: `new ReadOnlyDictionary(new SortedList(_dict))` was to correctly create a new sorted dictionary, and correct my use of the .ToDictionary(). But to be on the same page, based on this comment _no, quite the opposite...._ you are saying `new SortedList(_dict)` is the same as `_dict.ToDictionary()` in terms of the fact that the new dictionary is not guaranteed to be sorted. – MajorInc Oct 14 '16 at 17:40
  • I used `new ReadOnlyDictionary(new SortedList(_dict))` because that _does_ guarantee the dictionary is sorted. The `ReadOnlyDictionary()` class is only a wrapper; its members will have the ordering of whatever underling dictionary you give it. By using `SortedList`, that ensures the elements remain ordered. That's _very_ different from creating a whole new dictionary collection by passing it an existing one; in that case, the existing elements are _copied to_ the new one, and the new one remains unordered. – Peter Duniho Oct 14 '16 at 17:43
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/125733/discussion-between-majorinc-and-peter-duniho). – MajorInc Oct 14 '16 at 17:44
  • Even though you aren't using .NET 4.5, you can still use `ReadOnlyDictionary`. Just copy/paste from the open source: https://referencesource.microsoft.com/#mscorlib/system/collections/objectmodel/readonlydictionary.cs,81a03f90c06edeb7. IMHO, this is a preferable solution to making a whole new copy of the collection. – Peter Duniho Oct 14 '16 at 17:44
0

First of all, thanks to @Peter Duniho for the information and answer. I first attempted using reader writer locks, but had quite a bit of trouble and took the advise to use locks for simplicity. My solution is modifying Peters answer, because I did not want to lock everytime the ReadOnly dictionary was accessed.

And my ultimate goals for this class are:

  1. At any point in time, I would like to access the ReadOnly collection (and stop users to write to it).

  2. If something in the underlying dictionary gets added/removed, I do not want this to affect other's who are already using/reading/iterating the ReadOnly they have access to.

Here is the solution:

public class ThreadSafeSortedDictionary<TKey, TValue>
{
    private readonly SortedDictionary<TKey, TValue> _dict;
    private readonly object _sync;

    public ReadOnlyDictionary<TKey, TValue> ReadOnly { get; private set; }

    public IEnumerable<TValue> Values
    {
        get { return ReadOnly.Values; }
    }

    public IEnumerable<TKey> Keys
    {
        get { return ReadOnly.Keys; }
    }

    public int Count
    {
        get { return ReadOnly.Count; }
    }

    public ThreadSafeSortedDictionary(IComparer<TKey> comparer)
    {
        _dict = new SortedDictionary<TKey, TValue>(comparer);
        _sync = new object();
    }

    public void Add(TKey key, TValue value)
    {
        lock (_sync)
        {
            _dict.Add(key, value);
            SetReadOnlyDictionary();
        }
    }

    public void AddRange(IEnumerable<KeyValuePair<TKey, TValue>> keyValues)
    {
        if (keyValues == null) return;
        InternalAddRange(keyValues);
    }

    public void Remove(TKey key)
    {
        lock (_sync)
        {
            _dict.Remove(key);
            SetReadOnlyDictionary();
        }
    }

    public void Replace(IEnumerable<KeyValuePair<TKey, TValue>> newKeyValues)
    {
        //Throw exception, because otherwise we'll end up clearing the dictionary
        if (newKeyValues == null) throw new ArgumentNullException("newKeyValues");
        InternalAddRange(newKeyValues, true);
    }

    private void InternalAddRange(IEnumerable<KeyValuePair<TKey, TValue>> keyValues, bool clearBeforeAdding = false)
    {
        lock (_sync)
        {
            if (clearBeforeAdding) _dict.Clear();
            foreach (var kvp in keyValues)
            {
                _dict.Add(kvp.Key, kvp.Value);
            }
            SetReadOnlyDictionary();
        }
    }

    private void SetReadOnlyDictionary()
    {
        lock (_sync)
        {
            ReadOnly = new DictionaryReadOnly<TKey, TValue>(new SortedList<TKey, TValue>(_dict, _dict.Comparer));
        }
    }
}

And I used the ReadOnlyDictionary from the answer of Is there a read-only generic dictionary available in .NET?

I used SortedList vs SortedDictionary for the ReadOnly property, because I am creating them quite a few times (during add/remove/replace), and because 1. SortedList perform better in memory 2. are quicker in lookup, and as I am only using them for Read Only purposes, only lookup will be needed.

Community
  • 1
  • 1
MajorInc
  • 342
  • 3
  • 12