Является ли тип AutoResetEvent подходящим выбором для атомарного коммутатора? - PullRequest
4 голосов
/ 09 сентября 2010

Предположим, я обрабатываю большое количество входящих данных из нескольких потоков. Я могу захотеть, чтобы эти данные выступали в качестве триггера для конкретного действия при соблюдении определенных критериев. Тем не менее, действие не реентерабельное; таким образом, один и тот же триггер, запускаемый дважды в быстрой последовательности, должен запускать действие только один раз.

Использование простого флага bool для указания того, выполняется ли текущее действие, не является надежным решением, поскольку может возникнуть условие гонки, и два (или более) потока могут все же в конечном итоге выполнить одно и то же действие одновременно. Конечно, сработает bool в операторе lock объекта синхронизации; но я обычно предпочитаю избегать блокировок, когда это возможно *.

В настоящее время в этих случаях я использую AutoResetEvent, по сути, как атомный переключатель. Код обычно выглядит примерно так:

if (conditionMet && readyForAction.WaitOne(0))
{
    try
    {
        doAction();
    }
    finally
    {
        readyForAction.Set();
    }
}

Это работает, но мне кажется, что это может быть далеко не идеальный вариант использования для класса AutoResetEvent. Вы сказали бы, что это подходящее решение этой проблемы, или было бы более разумно использовать какой-то другой механизм?


Обновление : Как и Джон указал , используя Monitor.TryEnter в качестве стратегии блокировки (вместо простого lock, который, я считаю, примерно эквивалентен Monitor.Enter) , действительно довольно просто:

if (conditionMet && Monitor.TryEnter(lockObject))
{
    try
    {
        doAction();
    }
    finally
    {
        Monitor.Exit(lockObject);
    }
}

Тем не менее, я сильно склонен согласиться с Хенком об использовании Interlocked. Кажется, все так просто.

Ответы [ 2 ]

3 голосов
/ 09 сентября 2010

Простой (и быстрый) System.Threading.Interlocked.CompareExchange() подойдет.

//untested
int flag = 0;

if (conditionMet && Interlocked.CompareExchange(ref flag, 1, 0) == 0)
{
    try
    {
        doAction();
    }
    finally
    {
        flag = 0; // no need for locking
    }
}
2 голосов
/ 09 сентября 2010

«Я обычно предпочитаю избегать блокировок, когда это возможно» - почему? Похоже, вы эффективно делаете здесь нечто очень похожее, но используете примитивы синхронизации Windows вместо CLR. Вы пытаетесь синхронизировать так, чтобы только один поток мог одновременно выполнять определенный фрагмент кода ... это, конечно, звучит как блокировка для меня. Единственное отличие состоит в том, что если блокировка уже удерживается, вы вообще не хотите выполнять код.

Вы должны быть в состоянии достичь того же эффекта, используя Monitor.TryEnter, что, как я лично считаю, несколько проще для понимания. (Опять же, я «вырос» с мониторами Java, так что это ближе к моему прошлому.)

...