2016-02-23 11 views
1

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

Ниже приведена моя текущая реализация, но я не могу себе представить, что нет лучшего решения в структуре .net, что приводит к меньшему количеству строк кода.

object taskLock; 
    bool isRunning; 

    void Task() 
    { 
     if (isRunning) return; 

     try 
     { 
      lock (taskLock) 
      { 
       if (isRunning) return; 
       isRunning = true; 
      } 
      // Perform the magic 
     } 
     finally 
     { 
      isRunning = false; 
     } 
    } 
+0

вы могли бы использовать подобный подход, но использовать параллельный словарь вместо объекта taskLock. tbh -if в настоящее время работает, перемещается и разбивает другие элементы в вашем отставании –

+0

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

+0

Да, это может быть очень неэффективно, но я не вижу лучшей альтернативы сигналу для выполнения определенной задачи. Альтернативой может быть использование события сброса, когда задача просто ждет, пока другой поток не установит это событие, но я должен управлять потоком/задачей. –

ответ

1

Да, есть лучшее решение. Вы можете использовать Interlocked.CompareExchange, код становится более простым и безблокировочным:

class Worker 
{ 
    private volatile int isRunning = 0; 

    public void DoWork() 
    { 
     if (isRunning == 0 && Interlocked.CompareExchange(ref isRunning, 1, 0) == 0) 
     { 
      try 
      { 
       DoTheMagic(); 
      } 
      finally 
      { 
       isRunning = 0; 
      } 
     } 
    } 

    private void DoTheMagic() 
    { 
     // do something interesting 
    } 
} 

В этом случае Interlocked.CompareExchange делает следующее в качестве атомарной операции (псевдо-код):

wasRunning = isRunning; 
if isRunning = 0 then 
    isRunning = 1 
end if 
return wasRunning 

Из документации MSDN:

public static int CompareExchange(
    ref int location1, 
    int value, 
    int comparand 
) 

Если comparand и значение в LOCATION1 равны, то значение хранится в местоположении1. В противном случае операция не выполняется. Сравнение и операции обмена выполняются как атомная операция. Значение возвращения CompareExchange это исходное значение в LOCATION1, принимает ли не обмен места

+0

Мне нравится это решение, но не CompareExchange медленнее, чем возврат if (isRunning); '? Возможно выполнение 'if (isRunning == 1 || Interlocked.CompareExchange (ref isRunning, 1, 0) == 1) return;' может быть быстрее, когда задача будет выполняться чаще, а не –

+0

@RamonSmits, да это будет быстрее, но это должно быть 'if (isRunning == 0 && Interlocked.CompareExchange (ref isRunning, 1, 0) == 0)', и вам нужно объявить 'isRunning' как volatile. С другой стороны, 'Interlocked.CompareExchange' работает очень быстро. Я не верю, что вы получите ощутимую прибыль. –

+0

@ RamonSmits. Я отредактировал свой ответ, чтобы включить оптимизацию. Здесь у вас есть микро-бенчмарк, который сравнивает оптимизацию https://gist.github.com/jesuslpm/77b60126c8d787c7a76b –