День двести пятьдесят седьмой. #ЗаметкиНаПолях

Использование потокобезопасных коллекций. Начало

Потокобезопасные элементы работают правильно, когда используются из нескольких потоков (задач) одновременно. Стандартные коллекции .NET (List, Queue или Dictionary) не являются потокобезопасными. Библиотеки .NET предоставляют классы потокобезопасных коллекций, которые можно использовать при создании многозадачных приложений: BlockingCollection<Т>, ConcurrentQueue<Т>, ConcurrentStack<Т>, ConcurrentBag<Т>, ConcurrentDictionary<TKey, TValue>



ConcurrentQueue

Предоставляет потокобезопасную коллекцию, обслуживаемую по принципу "первым поступил — первым обслужен" (FIFO). Метод Enqueue добавляет элементы в очередь, а метод TryDequeue удаляет их. Обратите внимание, что, хотя метод Enqueue гарантированно сработает (очереди могут иметь бесконечную длину), метод TryDequeue вернет false в случае сбоя при извлечении из очереди.

Синхронизация обеспечивается внутри ConcurrentQueue<T>. Если два потока вызывают TryDequeue в один и тот же момент, ни одна из операций не блокируется. При обнаружении конфликта между двумя потоками, один поток должен попытаться снова получить следующий элемент.

TryDequeue пытается удалить элемент из очереди. Это происходит атомарно по отношению к другим операциям в очереди. Если очередь была заполнена элементами a, b и c, и два потока одновременно пытаются удалить из очереди элемент, один поток удалит из очереди a, а другой поток удалит из очереди b. Оба вызова TryDequeue вернут true, потому что они оба смогли удалить элемент из очереди. Если каждый поток попытается извлечь ещё по одному элементу, один из потоков удалит из очереди c и вернёт true, тогда как другой поток найдёт очередь пустой и вернет false.

Третий метод, TryPeek, позволяет программе проверить элемент в начале очереди, не извлекая его. Заметьте, что даже если метод TryPeek возвратит элемент, последующий вызов метода TryDequeue в том же потоке, для извлечения этого элемента из очереди, может завершиться неудачей, если элемент будет извлечён в другом потоке.



ConcurrentStack

Класс ConcurrentStack обеспечивает поддержку потокобезопасных стеков Элементы обслуживаются по принципу «первым пришёл – последним обслужен» (LIFO). Метод Push добавляет элементы в стек, а метод TryPop извлекает их. Существуют также методы PushRange и TryPopRange, которые можно использовать для добавления или извлечения нескольких элементов.



Можно перечислить элементы очереди или стека (программа может использовать конструкцию foreach для работы с каждым элементом в очереди). В начале перечисления параллельная очередь предоставит моментальный снимок содержимого.

Свойство Count возвращает количество элементов в коллекции, а свойство IsEmpty сообщает, является ли коллекция пустой (его предпочтительнее использовать, вместо сравнения Count с 0). Но вследствие многопоточного использования, значения этих свойств могут сразу же терять актуальность из-за действий в других потоках.



ConcurrentBag

Представляет потокобезопасный контейнер неупорядоченной коллекции объектов. Метод Add помещают элементы в коллекцию, а метод TryTake извлекает их. Существует также метод TryPeek, но он менее полезен в ConcurrentBag, поскольку возможно, что последующий метод TryTake вернет другой элемент.

Контейнеры полезны для хранения элементов, когда порядок не имеет значения, и в отличие от наборов, контейнеры поддерживают дублирование элементов. ConcurrentBag<T> оптимизирован для сценариев, в которых один и тот же поток будет как производить, так и потреблять данные, хранящиеся в контейнере. ConcurrentBag<T> может принимать null в качестве допустимого значения для ссылочных типов.



Источники:

- Rob Miles “Exam Ref 70-483 Programming in C#”. 2nd ed - Pearson Education, Inc., 2019. Глава 1.

-
https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent?view=netframework-4.8