День двести пятьдесят восьмой. #ЗаметкиНаПолях
Использование потокобезопасных коллекций. Продолжение
ConcurrentDictionary
Словарь предоставляет хранилище данных, проиндексированное по ключу.
Метод
Метод
Кроме того, хотя все методы
1) Поток A вызывает
2) Поток B одновременно вызывает
3) Продолжается выполнение делегата из потока A, он достигает блокировки, но теперь видит, что элемент уже существует.
4) Поток A выполняет «
Поэтому нет гарантии, что данные, возвращаемые
Источники:
- Rob Miles “Exam Ref 70-483 Programming in C#”. 2nd ed - Pearson Education, Inc., 2019. Глава 1.
- https://docs.microsoft.com/dotnet/standard/collections/thread-safe/how-to-add-and-remove-items
Использование потокобезопасных коллекций. Продолжение
ConcurrentDictionary
Словарь предоставляет хранилище данных, проиндексированное по ключу.
ConcurrentDictionary<TKey, TValue>
может использоваться несколькими параллельными задачами. Действия над словарем выполняются атомарно. Другими словами, действие по обновлению элемента в словаре не может быть прервано действием из другой задачи. ConcurrentDictionary
предоставляет некоторые дополнительные методы, которые необходимы, когда словарь используется несколькими задачами.ConcurrentDictionary<string, int> ages = new ConcurrentDictionary<string, int>();Метод
if (ages.TryAdd("Иван", 21))
Console.WriteLine("Иван успешно добавлен.");
Console.WriteLine("Возраст Ивана: {0}", ages["Иван"]);
// Пытаемся изменить возраст с 21 на 22
if (ages.TryUpdate("Иван", 22, 21))
Console.WriteLine("Возраст успешно обновлён");
Console.WriteLine("Новый возраст Ивана: {0}", ages["Иван"]);
// Увеличиваем возраст, используя фабричный метод
Console.WriteLine("Возраст Ивана обновлён до: {0}",
ages.AddOrUpdate("Иван", 1,
(name,age) => age = age+1));
Console.WriteLine("Новый возраст Ивана: {0}", ages["Иван"]);
TryAdd
пытается добавить новый элемент. Если элемент уже существует, метод TryAdd
возвращает false
. Метод TryUpdate
поставляется с ключом обновляемого элемента, новым значением, которое должно быть сохранено в элементе, и значением, которое должно быть перезаписано. В приведенном выше примере возраст элемента «Иван
», будет обновлен до 22
, только если существующее значение равно 21
. Это позволяет программе обновлять элемент, только если он имеет ожидаемое значение.Метод
AddOrUpdate
позволяет предоставить поведение, которое будет выполнять обновление заданного элемента или добавлять новый элемент, если он ещё не существует. В приведённом выше примере добавление элемента «Иван
» не будет выполнено при вызове AddOrUpdate
, поскольку элемент уже существует. Вместо этого выполняется пользовательский делегат, передаваемый в качестве третьего аргумента, который увеличивает возраст элемента «Иван
» на 1
.Метод
GetOrAdd
позволяет получить существующее значение для указанного ключа или, если ключ не существует, вы добавить пару ключ/значение. Методу GetOrAdd
также можно передать делегат для задания значения.ConcurrentDictionary
предназначен для многопоточных сценариев. Вам не нужно использовать блокировки в своем коде для добавления или удаления элементов из коллекции. Однако один поток всегда может извлечь значение, а другой поток - немедленно обновить коллекцию, задав этому же ключу новое значение.Кроме того, хотя все методы
ConcurrentDictionary
являются потокобезопасными, не все методы являются атомарными, в частности GetOrAdd
и AddOrUpdate
. Пользовательский делегат, который передается этим методам, вызывается вне внутренней блокировки словаря (это делается для того, чтобы неизвестный код не блокировал все потоки). Следовательно, возможна следующая последовательность событий:1) Поток A вызывает
GetOrAdd
, не находит элемента и создаёт новый элемент для добавления, вызывая делегат.2) Поток B одновременно вызывает
GetOrAdd
, вызывается его делегат, и он достигает внутренней блокировки словаря перед потоком A, поэтому его новая пара ключ-значение добавляется в словарь.3) Продолжается выполнение делегата из потока A, он достигает блокировки, но теперь видит, что элемент уже существует.
4) Поток A выполняет «
Get
» и возвращает данные, которые были ранее добавлены потоком B.Поэтому нет гарантии, что данные, возвращаемые
GetOrAdd
, являются теми же данными, которые создаются в делегате потока. Подобная последовательность событий может происходить и при вызове AddOrUpdate
.Источники:
- Rob Miles “Exam Ref 70-483 Programming in C#”. 2nd ed - Pearson Education, Inc., 2019. Глава 1.
- https://docs.microsoft.com/dotnet/standard/collections/thread-safe/how-to-add-and-remove-items