2016-10-11 5 views
0

Я попытался создать ThreadSafeSortedDictionary, и я работаю с .NET 4.0.C# ThreadSafeSortedDictionary Реализация с ограниченными методами

Я взглянул на Thread safe SortedDictionary, но не уверен в аспекте безопасности потока.

Любой думаю, что это может работать, или нет по причине я не могу видеть, связанные с:

  1. безопасности Thread - она ​​должна быть потокобезопасными
  2. Производительность/эффективность - Я не главно обеспокоен это (если не существует огромная проблема с производительностью)

Мой код:

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(); 
     } 
    } 
} 
+0

Если производительность не важна, почему бы не использовать ConcurentDictonary и GetSortedKeyValues ​​сделать какие-то значения? –

+0

Ок, думаю, я, вероятно, могу заменить SortedDictionary на ConcurrentDictionary. И затем сортируйте их, когда я устанавливаю словарь ReadOnly. Но потом я буду сортировать каждый раз, когда добавляю/удаляю что-то. Но это приведет к удалению некоторого кода при добавлении/удалении значений. – MajorInc

+0

Вам не нужно сортировать по добавлению или удалению, вам нужно только сортировать, когда вам нужно экспортировать данные. –

ответ

1

Я не вижу в вашем коде очевидных проблем с обеспечением безопасности потоков. На мой взгляд, большие вопросы имеют правильность:

  1. Незначительная проблема заключается в том, что ваш ReadOnly свойство имеет публичный сеттера. Это означает, что любой код вне класса может установить свойство в любое время без какой-либо синхронизации. Дизайн вашего класса должен отражать предполагаемое использование. Предположительно, вы не хотите, чтобы какой-либо другой код менял значение свойства, поэтому установщик должен быть private.
  2. Основная проблема заключается в том, что, хотя имя класса ThreadSafeSortedDict<TKey, TValue>, нет общедоступного представления данных отсортировано. Когда вы устанавливаете свойство ReadOnly, вы создаете новый несортированный словарь из исходных данных. Несмотря на то, что вы перечисляете исходный словарь по порядку, который сортируется, нет никаких гарантий того, что данные в новом словаре останутся в том порядке, в котором он был добавлен. Класс словаря представляет собой, по дизайну, неупорядоченный.

Если вы действительно хотите сохранить данные в SortedDictionary<TKey, TValue> объекта и представить отсортированный представление о нем, вам нужно использовать некоторый тип упорядоченной коллекции в качестве типа ReadOnly собственности. Это может быть еще SortedDictionary<TKey, TValue>, но поскольку цель состоит в том, чтобы коллекция была доступна только для чтения, IMHO имеет смысл сделать тип IReadOnlyDictionary<TKey, TValue>, возвращая объект ReadOnlyDictionary<TKey, TValue>. И вместо того, чтобы создавать новый объект каждый раз, когда отсортированный словарь изменен, вы можете инициализировать его лениво и сделать его недействительным при изменении сортированного словаря.

Например:

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; 
     } 
    } 
} 

Использование lock вместо замка читателя писатель не должен влиять на производительность значительно, если вообще, и делает код намного более явно потокобезопасной. Мне кажется, что вышеуказанного должно быть достаточно. Тем не менее, если это не так, некоторые улучшения, которые вы могли бы внести, включают в себя:

  1. Идти вперед и используя замок считывателя-писателя. Вам нужно будет приобрести блокировку считывателя в свойстве ReadOnly и обновить его до блокировки записи, если вам нужно установить поле _readOnly (т. Е. Если оно установлено на null).Это может улучшить производительность, если у вас есть очень много разногласий по поводу свойства ReadOnly.
  2. Не беспокоясь о нестандартном типе и вместо этого используйте ConcurrentDictionary<TKey, TValue>, просто скопировав его содержимое и отсортировав его после того, как вам понадобится отсортированный вид словаря. Является ли это полезным в вашем случае, я не могу сказать, поскольку в вопросе недостаточно контекста. Если вам по-прежнему потребуется реализовать некоторый тип синхронизации, чтобы избежать попытки получить отсортированное представление, когда какой-либо другой поток пытается изменить коллекцию, тогда вам, вероятно, понадобится какой-то тип обертки.
  3. Наличие вашего типа инструмента IReadOnlyDictionary<TKey, TValue>, а не наличие ReadOnly. Затем вы можете передать свой объект в виде словарного объекта только для чтения, если это необходимо, но код, который должен действительно модифицировать словарь, может все же сделать это с помощью исходного типа.

Учитывая, какая информация доступна в вопросе на данный момент, я бы сказал, что вариант № 3, вероятно, лучший. Это потребует самой работы, но мне кажется, что я предлагаю самый чистый и эффективный API. Вы бы не захотели иметь ReadOnly имущество и, вам не пришлось бы делать копию коллекции. Сам класс будет служить копией только для чтения, и он будет автоматически обновляться в отношении базовой коллекции словарей, поскольку реализация IReadOnlyDictionary<TKey, TValue> просто отнесется к базовому словарю (с синхронизацией, конечно) ,

+0

Tyvm Peter.As я на .NET 4.0 - у меня нет доступа к ReadOnlyDictionary или интерфейсу. Поэтому лучше всего использовать решение, которое вы написали. – MajorInc

+0

Peter, также относительно вашей другой точки, что «GetSortedKeyValues ​​(). ToDictionary (x => x.Key, x => x.Value)" не гарантирует, что результат Словаря будет отсортирован. Вы говорите, что переход в SortedList к конструктору Словаря: «Словарь (новый SortedList (_dict))» гарантирует, что новый Словарь будет в порядке лежащего в основе «_dict», который является SortedDictionary, относящимся к его «компаратору» ». – MajorInc

+0

На самом деле я думаю, что знаю ответ на ^^. Вместо этого мне нужно передать в компасторе, если и «idict»: «ReadOnly = новый словарь (новый SortedList (_dict, _dict.Comparer))» – MajorInc

0

Прежде всего, спасибо @Peter Duniho за информацию и ответ. Я впервые попытался использовать блокировки считывателя, но имел немало проблем и принял решение использовать замки для простоты. Мое решение изменяет Peters ответ, потому что я не хочу блокировки каждый раз, когда Доступ к словарю ReadOnly.

И мои конечные цели для этого класса:

  1. В любой момент времени, я хотел бы получить доступ к ReadOnly коллекции (и запретить пользователям писать в него).

  2. Если что-то в базовом словаре добавляется/удаляется, я не хочу, чтобы это влияет на друг, кто уже использует/чтение/итерацию ReadOnly они имеют доступ.

Вот решение:

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)); 
     } 
    } 
} 

И я использовал ReadOnlyDictionary из ответа Is there a read-only generic dictionary available in .NET?

я использовал SortedList против SortedDictionary для ReadOnly собственности , потому что я создаю их довольно много раз (во время добавления/r emove/replace), а потому, что 1. SortedList работают лучше в памяти. 2. Быстрее в поиске, и поскольку я использую их только для целей только для чтения, вам понадобится только поиск.

 Смежные вопросы

  • Нет связанных вопросов^_^