как заблокировать содержимое метода

У меня есть метод, который можно вызывать из многих потоков, но я просто хочу, чтобы 1-й поток выполнял некоторую логику внутри метода. Итак, я планирую использовать логическую переменную. Первый поток, который войдет, установит логическую переменную в false (чтобы предотвратить проникновение внутрь других потоков) и выполнит логику метода.

Последующие потоки, которые придут к этому методу, будут проверять логическую переменную. Поскольку 1-й поток установил для него значение false, они пропустят логику метода.

В коде примерно так:

private void myMethod()
{
   if (firsTime)  //set to true in the constructor
   {
      firstTime = false; //To prevent other thread to come inside here.
      //method logic
   }
}

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

Если я заблокирую «если», чтобы изменить firstTime на false, возможно, 2 или более потоков уже входят в «если» (не хочу этого).

Если я заблокирую за пределами «если», чтобы изменить firstTime на false, как 1-й поток может войти внутрь if для выполнения логики метода, если для firstTime уже установлено значение false ??

Мой вопрос: как сделать блокировку, чтобы иметь желаемую функциональность? (1-й поток, который приходит, устанавливает логическое значение и выполняет логику метода).

Я не могу заблокировать всю логику метода, так как это будут очень долгие операции.


person user2232787    schedule 09.08.2013    source источник
comment
Я бы просто вызвал логику метода в ctor, но, может быть, это только я :)   -  person Martin James    schedule 09.08.2013


Ответы (1)


Вы можете использовать Interlocked.Exchange для решения этой проблемы. Он установит значение данной переменной в указанное значение и вернет значение, которое раньше было в переменной, и все это будет сделано атомарно. Это гарантирует, что только один поток когда-либо будет запускать код в if:

private static int isFirst = 1;
public static void Foo()
{
    if (Interlocked.Exchange(ref isFirst, 0) == 1)
    {
        //DoStuff
    }
}

Обратите внимание, что Interlocked.Exchange не имеет перегрузки, которая принимает bool, поэтому вместо этого вы вынуждены использовать int (или какой-либо другой тип), используя 1 для true и 0 для false.

Если вам нужно решение, использующее lock, а не Interlocked, вы можете сделать это с помощью дополнительного локального значения bool:

private static bool isFirst = true;
private static object key = new object();
public static void Foo()
{
    bool amFirst;
    lock (key)
    {
        amFirst = isFirst;
        isFirst = false;
    }

    if (amFirst)
    {
        //DoStuff
    }
}
person Servy    schedule 09.08.2013
comment
Спасибо. Это решение для меня :) - person user2232787; 09.08.2013
comment
Думаю, я бы перефразировал Если вам нужно решение с использованием lock... на Если вы очень сильно хотите решение с использованием lock... - способу Interlocked намного легче следовать. - person Mathieu Guindon; 09.08.2013
comment
@retailcoder Я поставил решение Interlocked первым по какой-то причине, но что предпочтительнее, это в основном субъективно. Я хотел бы, чтобы решение Interlocked было намного лучше, если бы можно было использовать bool; вынужденное использование int в качестве bool немного ухудшает его читабельность. - person Servy; 09.08.2013
comment
Небольшой комментарий в коде может объяснить почему int... тем не менее, +1 за хороший ответ и предоставление обоих способов сделать это. - person Mathieu Guindon; 09.08.2013
comment
@retailcoder Я включил комментарий, объясняющий, почему вам нужно использовать int... "Note that Interlocked.Exchange has no overload that takes a bool, which is why you're forced to use an int (or some other type) instead, using 1 for true and 0 for false." - person Servy; 09.08.2013