从并发字典中获取所有值并清除它而不会丢失数据

Omu*_*Omu 14 .net c#

我正在将对象添加/更新到并发字典中并定期(每分钟)刷新字典,所以我的代码看起来像这样:

    private static ConcurrentDictionary<string, Metric> _metrics = new ConcurrentDictionary<string, Metric>();

    public static void IncrementCountMetricBy(string name, int count)
    {           
        _metrics.AddOrUpdate(....
    }

    public static Metric[] Flush()
    {
        var flushedMetrics = _metrics;
        _metrics = new ConcurrentDictionary<string, Metric>();
        return flushedMetrics.Values.ToArray();
    }
Run Code Online (Sandbox Code Playgroud)

现在我不确定这段代码是否有可能丢失一些对象/更新

Jon*_*eet 15

是的,你可能丢失一些数据:

  1. 递增的线程可以读取_metrics领域,并得到了老字典,然后被中断
  2. 然后,刷新线程_metrics用新字典替换该字段
  3. 冲洗线程比叫 Values.ToArray()
  4. 然后,递增线程调用AddOrUpdate不再被任何东西查看的字典.(它在步骤1中获取的那个)

换句话说,想象一下你的IncrementMetricCountBy方法实际上是:

public static void IncrementCountMetricBy(string name, int count)
{
    var tmp = _metrics;
    Thread.Sleep(1000);           
    tmp.AddOrUpdate(...);
}
Run Code Online (Sandbox Code Playgroud)

如果您可以看到为什么不安全,则相同的参数适用于您当前的代码.

据我所知,这里没有什么特别简单的事可做ConcurrentDictionary.一种选择是拍摄所有键的快照,然后将它们全部删除:

var keys = _metrics.Keys.ToList();
var values = new List<Metric>();
foreach (var key in keys)
{
    Metric metric;
    if (_metrics.TryRemove(key, out metric))
    {
        values.Add(metric);
    }
}
return values;
Run Code Online (Sandbox Code Playgroud)

返回时字典可能不为,但您不应丢失任何数据.(自方法启动以来,您可能会更新度量标准,并且在删除密钥后发生的任何更新将最终重新添加,但这应该没问题.)


Ser*_*rvy 5

它是.考虑以下情况:

  1. 线程一次调用AddOrUpdate,但在调用开始后立即停止执行,然后在该方法中执行任何操作(包括取出任何锁定).

  2. 线程两个副本字典的所有值.

  3. 线程一回去完成添加项目.

那个项目就会丢失.