Производитель Потребительская очередь не располагает - PullRequest
1 голос
/ 08 октября 2010

Я построил очередь Потребитель-производитель, обертывающую ConcurrentQueue .net 4.0 с сигнализацией SlimManualResetEvent между производящим (Enqueue) и потребляющим (в то время как (true) потоком на основе.*}

при вызове Dispose иногда блокирует соединение (один поток занимает), и метод dispose застревает. Я думаю, он зависает в ожидании resetEvents, но для этого я вызываю набор на disposeкакие-нибудь предложения?

Ответы [ 2 ]

2 голосов
/ 08 октября 2010

Обновление : Я понимаю вашу точку зрения о внутренней очереди. Мое предложение использовать BlockingCollection<T> основано на том факте, что ваш код содержит много логики для обеспечения блокирующего поведения. Написание такой логики самостоятельно очень подвержено ошибкам (я знаю это по опыту); поэтому, когда в рамках фреймворка существует существующий класс, который выполняет по крайней мере некоторые работы для вас, обычно предпочтительнее пойти с этим.

Полный пример того, как вы можете реализовать этот класс с использованием BlockingCollection<T>, слишком велик, чтобы включать в этот ответ, поэтому я разместил работающий пример на pastebin.com ; не стесняйтесь взглянуть и посмотреть, что вы думаете.

Я также написал пример программы, демонстрирующей приведенный выше пример здесь .

Мой код правильный? Я бы не сказал «да» с слишком большой уверенностью; в конце концов, я не писал модульных тестов, не проводил на нем никакой диагностики и т. д. Это просто базовый черновик, чтобы дать вам представление о том, как использование BlockingCollection<T> вместо ConcurrentQueue<T> очищает большую часть вашей логики (по моему мнению ) и облегчает фокусировку на основной цели вашего класса (потребление элементов из очереди и уведомление подписчиков), а не на несколько сложном аспекте его реализации (блокирующее поведение внутренняя очередь).


Вопрос, поставленный в комментарии:

По какой причине вы не используете BlockingCollection<T>?

Ваш ответ:

[...] мне нужна была очередь.

Из документации MSDN о конструкторе по умолчанию для BlockingCollection<T> класса :

Основной коллекцией по умолчанию является ConcurrentQueue<T>.

Если only причина, по которой вы решили реализовать свой собственный класс вместо BlockingCollection<T>, заключается в том, что вам нужна очередь FIFO, тогда ... возможно, вы захотите пересмотреть свое решение. BlockingCollection<T>, созданный с использованием конструктора по умолчанию без параметров , представляет собой очередь FIFO.

Тем не менее, хотя я не думаю, что смогу предложить всесторонний анализ размещенного вами кода, я могу, по крайней мере, предложить несколько указателей:

  1. Я бы очень не хотел использовать события так, как вы здесь, для класса, который имеет дело с таким хитрым многопоточным поведением. Вызывающий код может присоединять любые обработчики событий, которые он хочет, и они в свою очередь могут генерировать исключения (которые вы не перехватываете), блокировать на длительные периоды времени или, возможно, даже блокировать по причинам, совершенно не зависящим от вас - что очень плохо в случай блокировки очереди.
  2. В ваших методах 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> на самом деле не тот класс, который вам нужен для этого. Если вы можете предоставить больше информации, чтобы убедить меня, что это не так, возможно, я еще раз посмотрю.

0 голосов
/ 08 октября 2010

Поскольку _IsActive не помечено как volatile и нет никакого доступа lock для всего доступа, каждое ядро ​​может иметь отдельный кэш для этого значения, и этот кэш может никогда не обновиться. Так что пометка _IsActive в false в Dispose фактически не повлияет на все работающие потоки.

http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/

private volatile bool _IsActive=true;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...