День двести пятьдесят девятый. #ЗаметкиНаПолях
Использование потокобезопасных коллекций. Окончание
BlockingCollection<Т>
С точки зрения разработки лучше всего реализовывать паттерн «производитель/потребитель», то есть рассматривать задачи в многопоточном приложении как либо производителя, либо потребителя данных. Задача, которая и производит, и потребляет данные, уязвима для ситуаций «взаимной блокировки». Если задача A ожидает чего-то, создаваемого задачей B, а задача B ожидает чего-то, создаваемого задачей A, и ни одна из задач не может быть запущена.
Класс
Следующий код создаёт поток, который пытается добавить 5 элементов в коллекцию
Пример вывода:
Источник: Rob Miles “Exam Ref 70-483 Programming in C#”. 2nd ed - Pearson Education, Inc., 2019. Глава 1.
Использование потокобезопасных коллекций. Окончание
BlockingCollection<Т>
С точки зрения разработки лучше всего реализовывать паттерн «производитель/потребитель», то есть рассматривать задачи в многопоточном приложении как либо производителя, либо потребителя данных. Задача, которая и производит, и потребляет данные, уязвима для ситуаций «взаимной блокировки». Если задача A ожидает чего-то, создаваемого задачей B, а задача B ожидает чего-то, создаваемого задачей A, и ни одна из задач не может быть запущена.
Класс
BlockingCollection<T>
обеспечивает потокобезопасное средство добавления и извлечения элементов из хранилища данных. Он называется блокирующей коллекцией, потому что действие Take блокирует задачу, если нет элементов, которые необходимо извлечь. Попытки добавить элементы в заполненную коллекцию также блокируются. Кроме того, можно установить верхний предел размера коллекции. Следующий код создаёт поток, который пытается добавить 5 элементов в коллекцию
BlockingCollection
из 3 элементов. После добавления 3-го элемента этот поток блокируется. Программа также создаёт поток, который извлекает элементы из коллекции. Как только поток чтения начинает работать и извлекает некоторые элементы из коллекции, поток записи может продолжиться:BlockingCollection<int> data = new BlockingCollection<int>(3);
Task.Run(() =>
{
for(int i=1;i<=5;i++)
{
data.Add(i);
Console.WriteLine("Число {0} добавлено.", i);
}
data.CompleteAdding();
});
Console.ReadKey();
Console.WriteLine("Чтение элементов");
Task.Run(() =>
{
while (!data.IsCompleted)
{
try
{
int v = data.Take();
Console.WriteLine("Число {0} прочитано.", v);
}
catch (InvalidOperationException) { }
}
});
Console.ReadKey();
Пример вывода:
Число 1 добавленоЗадача добавления вызывает метод
Число 2 добавлено
Число 3 добавлено
Чтение элементов
Число 1 прочитано
Число 2 прочитано
Число 3 прочитано
Число 4 добавлено
Число 5 добавлено
Число 4 прочитано
Число 5 прочитано
CompleteAdding()
, после добавления последнего элемента. Это предотвращает добавление новых элементов в коллекцию. Извлекающая задача использует свойство IsCompleted
, чтобы определить, когда прекратить получать элементы. Свойство IsCompleted
возвращает true, когда коллекция пуста и вызван CompleteAdding
. Операция извлечения выполняется внутри конструкции try
-catch
, т.к. метод Take
может выбросить исключение, если между проверкой IsCompleted
и вызовом Take добавляющая задача вызовет CompleteAdding
. То есть получающая задача попытается выполнить извлечение из коллекции, которая была помечена как завершённая.BlockingCollection
также предоставляет методы TryAdd
и TryTake
, которые используются для попытки выполнения действия. Они возвращает true, если действие выполнено успешно. Кроме того, им можно передать максимальное время ожидания завершения действия и токен отмены. Класс BlockingCollection
выступает в качестве оболочки для других потокобезопасных классов коллекций, включая ConcurrentQueue
(используется по умолчанию), ConcurrentStack
и ConcurrentBag
.Источник: Rob Miles “Exam Ref 70-483 Programming in C#”. 2nd ed - Pearson Education, Inc., 2019. Глава 1.