Безопасно ли добавлять потоки в ConcurrentDictionary с помощью foo [bar] = baz? - PullRequest
2 голосов
/ 31 октября 2019

Я не фанат методов, прикрепленных к ConcurrentDictionary. AddOrUpdate требует функции, а GetOrAdd добавляет, когда вы просто ищете ключ, чтобы получить значение. Поэтому я хочу использовать его как обычный словарь (для безопасности я использую ContainsKey)

В документах говорится "Чтобы сохранить пару ключ / значение в словаре безоговорочно,и перезаписать значение ключа, который уже существует ", используйте средство установки индексатора: dictionary[key] = newValue.

Но эта операция все еще безопасна для потока? Комментарии внизу говорят: «Все открытые и защищенные члены ConcurrentDictionary являются поточно-ориентированными и могут использоваться одновременно из нескольких потоков». Но я не уверен на 100%, что они считают, что приведенный выше сеттер включен сюда.

Ответы [ 2 ]

2 голосов
/ 31 октября 2019

Вы можете проверить indexer реализацию ConcurrentDictionary на эталонном источнике - https://referencesource.microsoft.com/#mscorlib/system/Collections/Concurrent/ConcurrentDictionary.cs

    public TValue this[TKey key]
    {
        get
        {
            TValue value;
            if (!TryGetValue(key, out value))
            {
                throw new KeyNotFoundException();
            }
            return value;
        }
        set
        {
            if (key == null) throw new ArgumentNullException("key");
            TValue dummy;
            TryAddInternal(key, value, true, true, out dummy);
        }
    }

Если вы заметили, что установщик вызывает TryAddInternal, что является поточно-ориентированной реализацией

enter image description here

1 голос
/ 01 ноября 2019

Потокобезопасен в том смысле, что внутреннее состояние ConcurrentDictionary не будет повреждено. Это основная гарантия, предлагаемая этим классом. Так что если вы вызываете foo[bar] = fiz из одного потока и foo[bar] = biz из другого потока одновременно, то либо fiz, либо biz будет окончательно сохранено как значение ключа bar, при условии, что этот ключ уже существовал вcollection (иначе исключение будет выдано обоими потоками).

Это как если бы foo[bar] была переменной с размером собственного целого числа или меньше, например short count;. Безопасно обновить эту переменную из нескольких потоков, в том смысле, что ее внутренние биты не будут частично обновлены и всегда будут содержать последнее назначенное значение. Если этой гарантии недостаточно для вашей программы, например, если вы хотите, чтобы count содержал что-то значимое, например, сколько раз было обновлено, вам придется синхронизировать доступ к этой переменной.

В случае ConcurrentDictionary, если вы хотите, чтобы foo[bar] содержал значение biz, только если это было ранее fiz, тогда это , а не поточно-ориентированный: if (foo[bar] == fiz) foo[bar] = biz. Вам придется использовать метод TryUpdate: foo.TryUpdate(bar, biz, fiz). Другой случай: если вы хотите, чтобы foo[bar] содержал значение biz, только если оно было , а не ранее fiz, тогда вам не повезло. ConcurrentDictionary не включает метод, который предлагает эту гарантию. Таким образом, вы будете вынуждены вернуться назад, используя Dictionary + ручную синхронизацию.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...