С помощью ConcurrentQueue
вы можете безопасно вызывать методы Enqueue
и TryDequeue
из нескольких потоков параллельно. Здесь нет условий гонки. Вы можете делать это целый день 1000000 раз в секунду, без проблем (при условии, что вы не будете использовать всю доступную память в любой момент). Однако может возникнуть состояние гонки, если вы хотите подождать, пока предмет станет доступным, если его нет. Например, потребительский поток может работать в al oop следующим образом:
while (true)
{
if (!queue.IsEmpty)
{
queue.TryDequeue(out var item); // Race condition!
Process(item);
}
else
{
Thread.Sleep(50);
}
}
Этот код имеет условие состязания между вызовами на IsEmpty
и TryDequeue
. Тем временем очередь может быть освобождена другим потоком. Это условие гонки можно устранить, просто сняв проверку IsEmpty
:
while (true)
{
if (queue.TryDequeue(out var item)) // Fixed
{
Process(item);
}
else
{
Thread.Sleep(50);
}
}
Это неэффективно. Поток будет делать непроизводительные циклы, и когда элемент станет доступным, он получит его через некоторое время. Также обратите внимание, что в очереди нет способа уведомить поток о том, что он завершен, и больше никогда не будет больше элементов. Обе эти проблемы решаются с помощью специализированного класса BlockingCollection
.
foreach (var item in blockingCollection.GetConsumingEnumerable())
{
Process(item);
}
Метод GetConsumingEnumerable
обеспечивает мгновенное уведомление либо о новом элементе, либо для пополнения коллекции.
Класс BlockingCollection
имеет недостаток. Как следует из его названия, он блокирует текущий поток во время ожидания. Если это то, чего вы хотели бы избежать, вы можете посмотреть здесь для краткого обзора асинхронных альтернатив.