Подтвердить что ты не робот

Получение всех значений из параллельного словаря и очистка его без потери данных

Я добавляю/обновляю объекты в параллельный словарь и периодически (каждую минуту) смываю словарь, поэтому мой код выглядит примерно так:

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

теперь я не уверен, может ли этот код потерять некоторые объекты/обновления

4b9b3361

Ответ 1

Да, вы можете потерять некоторые данные там:

  • Инкрементный поток может прочитать поле _metrics и получить старый словарь, а затем прерваться
  • Затем поток смыва заменяет поле _metrics новым словарем
  • Поток для очистки, называемый Values.ToArray()
  • Приращающийся поток затем вызывает AddOrUpdate в словаре, на который больше ничего не смотрит. (Тот, который он выбрал на шаге 1.)

Другими словами, предположим, что ваш метод IncrementMetricCountBy на самом деле:

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

Если вы видите, почему это небезопасно, тот же аргумент применяется в вашем текущем коде.

Насколько я вижу, нет ничего особенного, что вы можете сделать с 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;

Словарь может не быть пустым при возврате, но вы не должны терять данные. (Вы можете обновлять показатели с момента запуска метода, и любое обновление, которое происходит после удаления ключа, закончится повторным добавлением, но это должно быть хорошо.)

Ответ 2

Это. Рассмотрим следующий случай:

  • Проведите один вызов AddOrUpdate, но прекратите выполнение сразу после начала вызова, прежде чем в нем будут предприняты какие-либо действия (включая снятие блокировок).

  • Вставьте две копии всех значений словаря.

  • Тема назад возвращается к завершению добавления элемента.

Этот элемент будет потерян.