При каких условиях TryDequeue и подобные методы System.Collections.Concurrent не работают

Недавно я заметил, что внутри объектов коллекции, содержащихся в пространстве имен System.Collections.Concurrent, обычно встречается Collection.TrySomeAction(), а не Collection.SomeAction().

В чем причина этого? Я предполагаю, что это имеет какое-то отношение к блокировке?

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

Ответ 1

Коллекции в пространстве имен System.Collections.Concurrent считаются потокобезопасными, поэтому их можно использовать для написания многопоточных программ, которые обмениваются данными между потоками.

До .NET 4 вам пришлось предоставить свои собственные механизмы синхронизации, если несколько потоков могут иметь доступ к одной общей коллекции. Вы должны были блокировать коллекцию каждый раз, когда вы изменяли ее элементы. Вам также может потребоваться блокировка коллекции каждый раз, когда вы обращаетесь к ней (или перечисляете ее). Это для простейших многопоточных сценариев. В некоторых приложениях создаются фоновые потоки, которые с течением времени доставляют результаты в общую коллекцию. Другой поток будет читать и обрабатывать эти результаты. Вам нужно было реализовать собственную схему передачи сообщений между потоками, чтобы уведомлять друг друга, когда были доступны новые результаты, и когда эти новые результаты были использованы. Классы и интерфейсы в System.Collections.Concurrent обеспечивают согласованную реализацию для тех и других общих задач многопоточного программирования, связанных с совместными данными через потоки, в режиме блокировки.

Try<something> имеет семантику - попробуйте выполнить это действие и вернуть результат операции. Семантика DoThat обычно использует исключенную механику для указания ошибки, которая может быть неэффективной. В качестве примеров они могут возвращать false,

  • Если вы попытаетесь добавить новый элемент, возможно, у вас уже есть его в ConcurentDictionary;
  • если вы попытаетесь получить элемент из коллекции, он может там не существовать;
  • Если вы попытаетесь обновить элемент, то может быть более поздний элемент уже, поэтому метод гарантирует, что он обновит только элемент, который должен был обновиться.

Попробуйте прочитать:

Ответ 2

Что вы имеете в виду с ошибкой?

Возьмем следующий пример:

var queue = new Queue<string>();
string temp = queue.Dequeue();
// do something with temp

Вышеприведенный код генерирует исключение, поскольку мы пытаемся удалить из пустой очереди. Теперь, если вместо этого вы используете ConcurrentQueue<T>:

var queue = new ConcurrentQueue<string>();
string temp;
if (queue.TryDequeue(out temp))
{
    // do something with temp
}

Вышеприведенный код не будет генерировать исключение. Очередь все равно не удалит элемент, но код не будет сбой при вызове исключения. Реальное использование для этого становится очевидным в многопоточной среде. Код для неконкурентного Queue<T> обычно выглядит примерно так:

lock (queueLock)
{
    if (queue.Count > 0)
    {
        string temp = queue.Dequeue();
        // do something with temp   
    } 
}

Чтобы избежать условий гонки, нам нужно использовать блокировку, чтобы гарантировать, что ничего не происходит с очередью за время, прошедшее с проверки Count, вызывающей Dequeue. С ConcurrentQueue<T> нам действительно не нужно проверять Count, но вместо этого можно называть TryDequeue.

Если вы исследуете типы, найденные в пространстве имен Systems.Collections.Concurrent, вы обнаружите, что многие из них переносят две операции, которые обычно называются последовательно, и традиционно требуется блокировка (Count, а затем Dequeue в ConcurrentQueue<T>, GetOrAdd в ConcurrentDictionary<TKey, TValue> заменяет последовательности вызова ContainsKey, добавляет элемент и получает его и т.д.).

Ответ 3

Если нет ничего, что можно было бы "отменить", например... Этот "Try-Pattern" обычно используется во всех элементах FCL и BCL. Это не имеет ничего общего с блокировкой, параллельные коллекции (или, по крайней мере, должны быть) в основном реализованы без блокировок...