【问题标题】:Make a snapshot and empty a ConcurrentDictionary制作快照并清空 ConcurrentDictionary
【发布时间】:2021-02-09 11:10:31
【问题描述】:

我有一个要清空的 ConcurrentDictionary,将其内容复制到字典(或任何其他容器)中,同时清空它。

一个关键的不变性是在操作过程中不会丢失源中的任何元素。

我当前的代码如下所示:

    private Dictionary<TKey, TValue> Foo<TKey, TValue>(ConcurrentDictionary<TKey, TValue> source)
    {
        var result = new Dictionary<TKey, TValue>();
        foreach (var item in source)
        {
            result[item.Key] = item.Value;
        }
        source.Clear();
        return result;
    }

据我了解,这段代码是线程安全的,但在 foreach 循环之后和 Clear() 之前同时添加的任何元素都将被清除。

编辑:更精确一些。在我的用例中,该代码是唯一删除某些字典键的代码;其他线程只使用 TryAdd 或 AddOrUpdate。

【问题讨论】:

  • 你为什么要这个?
  • 您可以使用 .ToDictionary() 方法复制项目,但您仍然需要阻止源字典写入,即在“writer”方法中添加一些标志,以防止它在您输入时输入正在复制。您可以即声明一个“volatile”布尔字段用作标志
  • @mjwills ImmutableDictionary 可能会比 ConcurrentDictionary 慢约 10 倍,而且分配也明显更多。它也不是并发的。每次更新时都需要同步。如果 OP 想要获取其值并经常清除它,这可能是一个有趣的解决方案,因为拍摄不可变集合的快照基本上是免费的。
  • 那么 ToArraydocs.microsoft.com/en-us/dotnet/api/… 会很好地满足您的需求。这允许您克隆字典,然后仅从原始字典中删除与您的克隆匹配的条目。
  • The invariant corresponds to the requirement that all collected values are logged at some point. 请注意,您并没有严格地满足这个标准 - 因为如果两个对同一个键的写入连续发生,您不一定会记录第一个.或许考虑两个数据结构——并发字典存储数据,并发队列记录数据?

标签: c# concurrency concurrentdictionary


【解决方案1】:

我能想到的唯一方法是TryRemove所有现有的键,一个一个:

public static Dictionary<TKey, TValue> RemoveAll<TKey, TValue>(
    this ConcurrentDictionary<TKey, TValue> source)
{
    var result = new Dictionary<TKey, TValue>();
    foreach (var key in source.Keys)
    {
        if (source.TryRemove(key, out var value)) result.Add(key, value);
    }
    return result;
}

Keys 属性返回ConcurrentDictionary 中键的快照,因此不可能包含两次相同的键。但它会产生争用,因为它acquires all internal locks。下面是一个使用 ConcurrentDictionary 的惰性枚举器的版本,因此它不会产生太多争用:

public static Dictionary<TKey, TValue> RemoveAll<TKey, TValue>(
    this ConcurrentDictionary<TKey, TValue> source)
{
    var result = new Dictionary<TKey, TValue>();
    foreach (var entry in source)
    {
        var (key, value) = entry;
        if (!result.ContainsKey(key) && source.TryRemove(key, out value))
            result.Add(key, value);
    }
    return result;
}

!result.ContainsKey(key) 的原因是枚举器产生两次相同键的(理论上)可能性。基于ConcurrentDictionarythis cannot happen的当前实现。

【讨论】:

  • 澄清:以上解决方案不是快照操作。它将删除并收集它“在旅途中”找到的任何键值对。
  • ToArray 将允许您记录快照值,如果您想这样做的话。您甚至可以使用docs.microsoft.com/en-us/dotnet/api/… 来确保从ConcurrentDictionary 中删除的条目具有相同的值(此后未发生突变)。
  • @mjwillsToArray 获取所有内部锁,因此会导致所有与ConcurrentDictionary 交互的线程在生成快照时被阻塞。这也是我回答中RemoveAll第一版的问题。
  • 是的。 ToArray 的好处是您的日志记录可以反映特定时间点的键和值(快照)。
  • @mjwills 如果您尝试删除快照中不再存在的键值对,您将如何处理它们?如果您不将它们包含在输出中,那么我们就不能再谈论快照了。快照的子集不是快照。
猜你喜欢
  • 1970-01-01
  • 2017-04-25
  • 1970-01-01
  • 1970-01-01
  • 2021-11-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多