【问题标题】:C# ThreadSafeSortedDictionary Implementation with limited methods具有有限方法的 C# ThreadSafeSortedDictionary 实现
【发布时间】:2016-10-11 15:42:35
【问题描述】:

我已尝试创建 ThreadSafeSortedDictionary,并且正在使用 .NET 4.0。

我看过Thread safe SortedDictionary,但对线程安全方面没有信心。

任何人都认为这可以工作,或者不是因为我看不到与以下相关的原因:

  1. 线程安全 - 它必须是线程安全的
  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();
        }
    }
}

【问题讨论】:

  • 如果性能不重要,为什么不使用 ConcurentDictonary 并在 GetSortedKeyValues 上做一些值?
  • 好的,我想我可能可以用 ConcurrentDictionary 替换 SortedDictionary。然后在我设置 ReadOnly 字典时对它们进行排序。但是每次我添加/删除某些东西时,我都会进行排序。但这会在添加/删除值时删除一些代码。
  • 添加或删除不需要排序,需要导出数据时才需要排序
  • 如果是这种情况,那么是的,因为将排序放在添加删除部分
  • 那你还有什么问题?

标签: c# collections .net-4.0 thread-safety


【解决方案1】:

我没有看到您的代码有任何明显的线程安全问题。对我来说,更大的问题是正确性

  1. 一个小问题是您的ReadOnly 属性有一个公共设置器。这意味着类外的任何代码都可以随时设置属性,无需任何同步。你的类设计应该反映预期的用途。想必你不希望任何其他代码更改属性值,所以 setter 应该是private
  2. 一个主要问题是,虽然类名是ThreadSafeSortedDict&lt;TKey, TValue&gt;,但没有可公开访问的排序数据视图。当您设置 ReadOnly 属性时,您将根据原始数据创建一个新的 unsorted 字典。即使您按顺序枚举源字典(已排序),也不能保证新字典中的数据将保持添加时的顺序。按照设计,字典类是一个 无序 集合。

如果您真的想在SortedDictionary&lt;TKey, TValue&gt; 对象中维护数据,并显示它的排序视图,您需要使用某种类型的有序集合作为您的ReadOnly 属性类型。这可能是另一个SortedDictionary&lt;TKey, TValue&gt;,但由于该集合的目的是只读的,恕我直言,将类型设置为IReadOnlyDictionary&lt;TKey, TValue&gt; 更有意义,返回一个ReadOnlyDictionary&lt;TKey, TValue&gt; 对象。而不是每次修改排序字典时都创建一个新对象,您可以延迟初始化它并在修改排序字典时使其无效。

例如:

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&lt;TKey, TValue&gt;,当您需要字典的排序视图时,只需复制其内容并在事后对它们进行排序。我不能说这对您的情况是否有用,因为问题中没有足够的上下文。如果您仍然需要实现某种类型的同步以避免在其他线程尝试修改集合时尝试检索排序视图,那么您可能仍然需要某种包装类型。
  3. 让您的类型实现IReadOnlyDictionary&lt;TKey, TValue&gt;,而不是拥有ReadOnly 属性。然后,您可以在需要时将对象作为只读字典对象传递,但需要实际修改字典的代码仍然可以通过原始类型进行。

鉴于目前问题中的可用信息,我会说选项 #3 可能是最好的。这将需要最多的工作,但在我看来,它提供了最干净、最有效的 API。您将避免必须拥有ReadOnly 属性并且您将避免必须制作集合的副本。该类本身将用作只读副本,并且它会自动保持与底层字典集合相关的最新状态,因为IReadOnlyDictionary&lt;TKey, TValue&gt; 的实现将只遵循底层字典(通过同步,当然)。

【讨论】:

  • Tyvm Peter.As 我在 .NET 4.0 上 - 我无权访问 ReadOnlyDictionary 或界面。所以我最好的选择是使用你写的解决方案。
  • Peter,关于你的另一点,“GetSortedKeyValues().ToDictionary(x => x.Key, x => x.Value)”不保证结果字典会被排序。您是说将 SortedList 传递给 Dictionary 的构造函数:“Dictionary(new SortedList(_dict))”将保证新 Dictionary 将按照底层“_dict”的顺序,这是一个 SortedDictionary,尊重其“比较器” "。
  • 其实我想我知道答案^^。相反,如果 _dict 也需要传入比较器:“ReadOnly = new Dictionary(new SortedList(_dict, _dict.Comparer))”
  • 在底层排序字典上使用 ToDictionary 分配 ReadOnly 时,ReadOnly 字典确实在该时间点排序。但我同意 ReadOnly 字典在添加新数据时不会保持排序,并且新添加的数据将以未排序的方式添加。 (但在我的上下文中,不应将数据添加到 ReadOnly 字典中,所以我认为我会没事的)
  • "你是说传入一个 SortedList 给 Dictionary 的构造函数:"Dictionary(new SortedList(_dict))" 将保证新的 Dictionary 将按照底层的“_dict””——不,恰恰相反。无论您使用ToDictionary() 还是将IDictionary 对象传递给构造函数,new 字典都将是无序的。它可能以与原始元素相同的顺序结束,但这不能保证,如果字典被修改,可能会改变。您不能假设除 Sorted... 类型之外的任何字典类型都是有序的。
【解决方案2】:

首先,感谢@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));
        }
    }
}

我使用了Is there a read-only generic dictionary available in .NET?的回答中的ReadOnlyDictionary

我将 SortedListSortedDictionary 用于 ReadOnly 属性,因为我多次创建它们(在添加/删除/替换期间),并且因为 1. SortedList 在内存中表现更好 2. 查找速度更快,并且由于我仅将它们用于只读目的,因此只需要查找。

【讨论】:

    猜你喜欢
    • 2015-10-08
    • 1970-01-01
    • 2011-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多