Существует несколько возможных способов в зависимости от того, что именно вы хотите реализовать.
Имеются реализации интерфейса IProducerConsumerCollection<T>
. Насколько мне известно, единственная потокобезопасная реализация этого интерфейса в .NET Framework - это BlockingCollection<T>
.
Этот класс позволяет вам блокировать или не блокировать производителей и потребителей. Сторона производителя устанавливается между блокировкой и неблокированием, предоставляя ограничение емкости коллекции в конструкторе. В документации по BlockingCollection<T>.Add(T)
методу состояний:
Если ограниченная емкость была указана при инициализации этого экземпляра BlockingCollection<T>
, вызов Добавить может блокировать до тех пор, пока пространство доступно для хранения предоставленного товара.
для извлечения элементов, которые можно использовать различные Take
и TryTake
методы или чрезвычайно удобный BlockingCollection<T>.GetConsumingEnumerable()
метод, который создает IEnumerable<T>
, что создает IEnumerator<T>
, который потребляет один элемент из BlockingCollection<T>
при извлечении следующего значения и блокировки в случае, если источник коллекция пуста. То есть до тех пор, пока не вызывается BlockingCollection<T>.CompleteAdding()
, и коллекция не принимает никаких новых данных. На данный момент все экземпляры, потребляющие перечисляемые экземпляры будут прекратить блокирование и сообщать о том, что нет никаких данных больше
Таким образом, вы можете в основном реализовать потребителя, как это (как только все остальные данные израсходован.):
BlockingCollection<...> bc = ...
foreach (var item in bc.GetConsumingEnumerable())
{
// do something with your item
}
Такой потребитель может быть запущен в нескольких потоках, чтобы вы могли просматривать несколько потоков из своего источника, если захотите. Вы можете создать любое количество потребляющих счетчиков.
Вы должны знать, что эта коллекция действительно только обертка. Существует конструктор, который позволяет вам установить тип используемой коллекции. По умолчанию ConcurrentQueue<T>
.Это означает, что по умолчанию коллекция ведет себя как эта очередь и является коллекцией First-In-First-Out, если вы используете только одного производителя и одного потребителя.
Все, что называется, есть альтернатива. Если вам не нужна блокирующая часть (или вы хотите реализовать блокирующую часть самостоятельно), и если вам не нужен какой-либо порядок элементов внутри вашей коллекции, есть ConcurrentBag<T>
. Эта коллекция обрабатывает доступ из нескольких потоков, очень эффективно. Он использует меньшие коллекции внутри оберток ThreadLocal<T>
. Таким образом, каждый поток использует свое собственное хранилище, и только если в потоке заканчиваются элементы в собственном хранилище, он начинает получать элементы из другого хранилища потоков.
Использование этой коллекции может быть интересно в случае, если изготовление и потребление происходит последовательно в вашем случае использования. Таким образом, вы сначала добавляете все элементы, и как только это будет сделано, вы будете потреблять все предметы, как с несколькими потоками.
Я сделал неправильное предположение о том, что 'BlockingCollection' будет медленно с множеством элементов. После тестирования он отлично работает с высокой пропускной способностью. –