Обновление : Я понимаю вашу точку зрения о внутренней очереди. Мое предложение использовать BlockingCollection<T>
основано на том факте, что ваш код содержит много логики для обеспечения блокирующего поведения. Написание такой логики самостоятельно очень подвержено ошибкам (я знаю это по опыту); поэтому, когда в рамках фреймворка существует существующий класс, который выполняет по крайней мере некоторые работы для вас, обычно предпочтительнее пойти с этим.
Полный пример того, как вы можете реализовать этот класс с использованием BlockingCollection<T>
, слишком велик, чтобы включать в этот ответ, поэтому я разместил работающий пример на pastebin.com ; не стесняйтесь взглянуть и посмотреть, что вы думаете.
Я также написал пример программы, демонстрирующей приведенный выше пример здесь .
Мой код правильный? Я бы не сказал «да» с слишком большой уверенностью; в конце концов, я не писал модульных тестов, не проводил на нем никакой диагностики и т. д. Это просто базовый черновик, чтобы дать вам представление о том, как использование BlockingCollection<T>
вместо ConcurrentQueue<T>
очищает большую часть вашей логики (по моему мнению ) и облегчает фокусировку на основной цели вашего класса (потребление элементов из очереди и уведомление подписчиков), а не на несколько сложном аспекте его реализации (блокирующее поведение внутренняя очередь).
Вопрос, поставленный в комментарии:
По какой причине вы не используете BlockingCollection<T>
?
Ваш ответ:
[...] мне нужна была очередь.
Из документации MSDN о конструкторе по умолчанию для BlockingCollection<T>
класса :
Основной коллекцией по умолчанию является ConcurrentQueue<T>
.
Если only причина, по которой вы решили реализовать свой собственный класс вместо BlockingCollection<T>
, заключается в том, что вам нужна очередь FIFO, тогда ... возможно, вы захотите пересмотреть свое решение. BlockingCollection<T>
, созданный с использованием конструктора по умолчанию без параметров , представляет собой очередь FIFO.
Тем не менее, хотя я не думаю, что смогу предложить всесторонний анализ размещенного вами кода, я могу, по крайней мере, предложить несколько указателей:
- Я бы очень не хотел использовать события так, как вы здесь, для класса, который имеет дело с таким хитрым многопоточным поведением. Вызывающий код может присоединять любые обработчики событий, которые он хочет, и они в свою очередь могут генерировать исключения (которые вы не перехватываете), блокировать на длительные периоды времени или, возможно, даже блокировать по причинам, совершенно не зависящим от вас - что очень плохо в случай блокировки очереди.
- В ваших методах
Dequeue
и Dispose
есть расы.
Посмотрите на эти строки вашего Dequeue
метода:
if (_IsActive) // point A
{
_mres.Wait(); // point C
_mres.Reset(); // point D
}
А теперь взгляните на эти две строки из Dispose
:
_IsActive = false;
_mres.Set(); // point B
Допустим, у вас есть три потока: T 1 , T 2 и T 3 . T 1 и T 2 находятся в точке A , где каждый проверяет _IsActive
и находит true
. Затем вызывается Dispose
, и T 3 устанавливает _IsActive
в false
(но T 1 и T 2 уже прошли точку A ) и затем достигает точки B , где она вызывает _mres.Set()
. Затем T 1 попадает в точку C , переходит в точку D и вызывает _mres.Reset()
. Теперь T 2 достигает точки C и будет зависать навсегда, поскольку _mres.Set
больше не будет вызываться (любой поток, выполняющий Enqueue
, найдет _IsActive == false
и немедленно вернется, а поток, выполняющий Dispose
, уже прошел точку B ).
Я был бы рад попытаться помочь с решением этого условия гонки, но я скептически отношусь к тому, что BlockingCollection<T>
на самом деле не тот класс, который вам нужен для этого. Если вы можете предоставить больше информации, чтобы убедить меня, что это не так, возможно, я еще раз посмотрю.