Возврат к нулю
Я пытаюсь использовать CountdownEvent, чтобы позволить потокам продолжать работу только тогда, когда счетчик событий равен нулю, однако я бы хотел, чтобы начальный счетчик был равен нулю. По сути, я хотел бы вернуться к нулевому поведению, при котором событие сигнализируется всякий раз, когда счетчик равен нулю, и потоки вынуждены ждать, когда он больше нуля.
Я могу инициализировать событие обратного отсчета с 0 начальным счетом, но когда я пытаюсь добавить к счету, я получаю InvalidOperationException "CountdownEvent_Increment_AlreadyZero".
Есть ли альтернативный класс или другой способ, которым я могу использовать событие обратного отсчета, чтобы избежать этого ограничения?
8 ответов
Вы написали:
Я выполняю операцию, которая создаст неизвестное количество дочерних операций (не задач или потоков)
Так что они? Вы должны сделать что-то вроде этого:
CountdownEvent ev; public void foo () { ev = new CountdownEvent (1); foreach () { ev.AddCount (); // введите код, который запускает вашу задачу } ev.Signal(); ev.Wait(); } public static void youtTask(CountdownEvent ev) { // некоторые работы //... // после того, как все сделано ev.Signal(); }
Если вы можете использовать.NET 4.0 или Reactive Extensions для.NET 3.5 (который имеет бэкпорт функций.NET 4 TPL), вы можете проверить класс Barrier. Это позволяет координировать несколько параллельных задач, чтобы они не продолжались до тех пор, пока все участники барьера не оповестят о своем прибытии. Он также должен соответствовать вашему требованию, чтобы участники появлялись и исчезали в процессе обработки.
Поэтому, по сути, вам нужен "включатель / выключатель", а не объект синхронизации, который можно настроить с произвольным обратным отсчетом. CountdownEvent
не подходит для таких случаев.
Почему бы тебе просто не использовать Semaphore
с начальным счетом один?
Будет ли это работать для вас? http://msdn.microsoft.com/en-us/library/dd384749.aspx
редактировать
Извините, это было расплывчато. Используя ответ SOReader, где у каждого родителя есть событие обратного отсчета, начиная с 1, затем используя TryAddCount для дочерних приращений, а затем уменьшая родительские значения до 1, затем в родительском уменьшении от 1 до нуля, когда дети завершены, и, наконец, уменьшите счет в родитель родительского потока. Итак, древовидная серия отсчетов событий.
У меня нет опыта работы с многопоточностью, но на первый взгляд я бы попробовал.
Ваш вопрос, кажется, является обычной техникой разветвления. Каждый раз, когда вы бы повторили, вы вместо этого запускаете другую параллельную операцию (ставьте ее в очередь потоков и т. Д.). Но вам нужно дождаться окончания всех ветвей в конце. Просто добавьте 1 к событию обратного отсчета для каждой подоперации, которую вы запускаете, и сообщите об этом в конце каждой подоперации. Это безопасно делать до тех пор, пока вы настроите алгоритм, чтобы он не сигнализировал, пока не добавит для каждой дочерней операции.
Я должен добавить, что вам не нужно знать счет заранее, просто сделайте его 1 в корне, и каждый раз, когда вы переходите к дочернему элементу, добавляйте 1, затем сигнализируйте в конце каждого, и он будет динамически обращаться с любым деревом без предварительной оплаты.
У CountdownEvent есть метод Add, который позволяет увеличить счетчик в полете.
Имеет ли это смысл? Я могу быть далеко от того, что вы пытаетесь достичь.
Однако, если вы действительно хотите CountdownEvent, который ведет себя так, как вы указали, довольно просто обернуть пару взаимосвязанных операций в классе, чтобы сделать то, что вы говорите.
Тем не менее, CountdownEvent создан так, чтобы быть легковесным, он почти бесплатен, если никто не ждет, пока его не оповестят. В дорогом случае это оптимально, независимо от того, сколько задач (и т. Д.) Ему потребуется только для одного перехода ядра в сигнал и одной для ожидания, в худшем случае.
Для реализации того, что вы предлагаете, потребуется синхронизация вокруг сигнализации и сброса события. Событие обратного отсчета основывается на одном простом принципе, только переход от ненулевого к нулю при вызове Сигнала может сигнализировать о событии. Расы нет, поскольку более чем один поток не может изменить значение за один раз (он заблокирован), поэтому только один поток может попытаться сигнализировать объект события (который пробуждает другой ожидающий поток). Отлично.
Однако, если у вас есть несколько потоков, устанавливающих и сбрасывающих его, вам нужно синхронизировать все вокруг набора и сброса, так как счетчик может несколько раз дрожать, и несколько потоков будут одновременно пытаться установить или сбросить событие. (Установка, сброс и ожидание события - все это дорого, потому что все они должны выполнить переход ядра и вызвать переключение контекста). Это не сработает, если вы не синхронизируете что-то, чтобы защитить переходы установки / сброса. Если бы они добавили это в CountdownEvent, это больше не было бы почти оптимальным, это было бы значительно дороже.
Как насчет семафоров: http://msdn.microsoft.com/en-us/library/system.threading.semaphore.aspx
Изменить: В следующем посте обсуждается, почему то, что вы описываете, не рекомендуется, а также предлагается обходной путь: http://social.msdn.microsoft.com/Forums/en/parallelextensions/thread/aa49f92c-01a8-4901-9846-91bc1587f3ae
Вы можете использовать объект Queue, чтобы добавить "работу" и вынуть снова.
Только когда очередь пуста, вы двигаетесь дальше.
Но да, нам здесь понадобится конкретика...
Я сталкиваюсь с той же проблемой, но в контексте Barrier
,
На самом деле, если вы думаете об этом, CountdownEvent
это одна фаза Barrier
,
Таким образом, чтобы избежать ограничения:
Я могу инициализировать событие обратного отсчета с 0 начальным счетом, но когда я пытаюсь добавить к счету, я получаю InvalidOperationException "CountdownEvent_Increment_AlreadyZero".
Вы можете просто перейти к Barrier
и использовать его AddParticipant
или же RemoveParticipan
методы.