2013-06-24 3 views
5

У меня есть служба Windows (.NET 4), которая периодически обрабатывает очередь, например каждые 15 минут. Я использую System.Threading.Timer, который устанавливается, когда служба запускает обратный вызов каждые X миллисекунд. Обычно каждый запуск занимает несколько секунд и никогда не сталкивается, но что, если я не могу этого принять, - тогда я хочу, чтобы следующий запуск был немедленно завершен, если обработка выполняется.Монитор, замок или энергозависимый?

Это легко решить с помощью блокировки, volatile bool или монитора, но что на самом деле целесообразно использовать в этом сценарии или просто предпочтительный вариант в целом?

Я нашел другие сообщения, которые отвечают почти на этот сценарий (например, Volatile vs. Interlocked vs. lock), но для этого нужно несколько советов по расширению этого примера Timer с немедленным выходом.

ответ

6

Для этого вам не нужны блокировки, вы должны просто перенести выполнение следующего таймера из делегата таймера. Это должно обеспечить 100% -ное перекрытие.

В конце вызова обработчика события таймера timer.Change(nextRunInMilliseconds, Timeout.Infinite), таким образом таймер будет срабатывать только один раз, после nextRunInMilliseconds.

Пример:

//Object that holds timer state, and possible additional data 
private class TimerState 
{ 
    public Timer Timer { get; set; } 
    public bool Stop { get; set; } 
} 

public void Run() 
{ 
    var timerState = new TimerState(); 
    //Create the timer but don't start it 
    timerState.Timer = new Timer(OnTimer, timerState, Timeout.Infinite, Timeout.Infinite); 
    //Start the timer 
    timerState.Timer.Change(1000, Timeout.Infinite); 
} 

public void OnTimer(object state) 
{ 
    var timerState = (TimerState) state;    
    try 
    { 
     //Do work 
    } 
    finally 
    { 
     //Reschedule timer 
     if (!timerState.Stop) 
      timerState.Timer.Change(1000, Timeout.Infinite); 
    } 
} 
+1

+1, лучше предотвратить перекрытие в первую очередь. Но не следует ли это сочетать с * отключением * таймера полностью в * начале * обработки, используя 'timer.Change (Timeout.Infinite, Timeout.Infinite)'? – shambulator

+0

@shambulator: +1 для отключения таймера в начале событияHandler. Как правило, что-то вроде «timer.Change (Timeout.Infinite, Timeout.Infinite)» при запуске и в конце обработчика событий (после цикла обработки) восстанавливает таймер в его исходное состояние. –

+2

@shambulator: таймер * отключен в начале обработки. Передача 'Timeout.Infinite' в качестве параметра' period' приведет к срабатыванию таймера только один раз, поэтому после запуска делегата таймер уже * отключен *. –

1

Ну, любой из них будет делать эту работу. Monitorобычно довольно прост в использовании через lock, но вы не можете использовать lock в этом случае, потому что вам нужно указать нулевой тайм-аут; как таковой, простейший подход, вероятно, в CompareExchange:

private int isRunning; 
... 
if(Interlocked.CompareExchange(ref isRunning, 1, 0) == 0) { 
    try { 
     // your work 
    } finally { 
     Interlocked.Exchange(ref isRunning, 0); 
    } 
} 

сделать то же самое с Monitor является:

private readonly object syncLock = new object(); 
... 
bool lockTaken = false; 
try { 
    Monitor.TryEnter(syncLock, 0, ref lockTaken); 
    if (lockTaken) { 
     // your work 
    } 
} finally { 
    if(lockTaken) Monitor.Exit(syncLock); 
} 
0

Я думаю, что если вы обнаружите, что вам нужно для синхронизации делегата таймера - вы делаете это неправильно, и Timer, вероятно, не тот класс, который вы хотите использовать. Имхо его лучше:

1) либо сохранить Timer, но увеличить значение интервала до точки, где его с уверенностью предположить, что не будет никаких проблем с резьбой,

2) или удалить Timer и вместо этого используйте просто Thread. Вы знаете, что-то вроде:

var t = new Thread(); 
t.Start(() => 
     { 
      while (!_stopEvent.WaitOne(100)) 
      { 
       .......... 
      } 
     });