2012-01-30 4 views
11

У меня есть List<System.Threading.Timer>. Каждый таймер запускается с настраиваемым интервалом (по умолчанию 10 минут). Все вызовы используют один и тот же метод обратного вызова (с другим параметром). Метод завершения вызова может занять несколько секунд, чтобы завершить его работу.Ждать System.Threading.Timer Callbacks для завершения до выхода из программы

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

Как я могу элегантно дождаться завершения всех выполняемых в настоящее время методов обратного вызова до выхода из программы?

ответ

16

Вы можете Утилизируйте все таймеры с параметром WaitHandler. Этот обработчик будет сигнализировать только тогда, когда метод обратного вызова завершен (как говорит спецификация: «Таймер не не расположен, пока все в настоящее время в очереди обратных вызовов завершены»)

void WaitUntilCompleted(List<Timer> myTimers) 
{ 
    List<WaitHandle> waitHnd = new List<WaitHandle>(); 
    foreach (var timer in myTimers) 
    { 
     WaitHandle h = new AutoResetEvent(false); 
     if(!timer.Dispose(h)) throw new Exception("Timer already disposed."); 
     waitHnd.Add(h); 
    } 
    WaitHandle.WaitAll(waitHnd.ToArray()); 
} 

Edit: @Peter подчеркнул важность метода Dispose возвращаемое значение. Он возвращает false, когда таймер уже установлен. Чтобы убедиться, что это решение остается надежным, я изменил его, чтобы исключить исключение, когда Таймер уже настроен, поскольку мы не можем контролировать в таком случае, когда его обратный вызов заканчивается, несмотря на то, что предыдущий обратный вызов удаления может быть запущен!

+0

Я только что протестировал это, и @Peter прав: вы можете зайти в тупик, если у вас есть несколько раз. Его ответ правильный. –

+0

Я не согласен с решением @Peter, прочитал мое Редактирование. – Tomek

-2

Вероятно, ждать выхода в консольном приложении не представляется возможным.

Для приложения Windows Forms:

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

И тогда вы можете проверить соответствующее событие и ждать ли счетчик, чтобы он стал 0 или просто отменить выход.

+0

действительно, это ;-) http://msdn.microsoft.com/en-gb/library/system.console.cancelkeypress(v=vs. 80) .aspx – Seb

+0

Решение Tomek отлично работает в консольном приложении. Ваше решение будет работать и в консольном приложении.Приложение должно было бы опросить счетчик, ожидая, пока он достигнет 0 до выходов Main. –

1

Чтобы заблокировать основной поток, вы можете использовать ManualResetEvents, пока не завершится незавершенная операция.

, например, если вы хотите всех таймеров, чтобы выполнить по крайней мере один раз, то вы могли бы иметь System.Threading.ManualResetEvent[] массив с начальным состоянием, установленным в несигнальный

Так где-то в вашем коде вы бы ваши настройки таймера и это связано с инициализацией waithandle.

// in main setup method.. 
int frequencyInMs = 600000; //10 mins 
Timer timer = new Timer(); 
timer.Elapsed += (s, e) => MyExecute(); 
myTimers.Add(timer) 

ManualResetEvent[] _waithandles = new ManualResetEvent[10]; 
_waithandles[0] = new ManualResetEvent(false); 

// Other timers ... 
timer = new Timer(); 
timer.Elapsed += (s, e) => MyOtherExecute(); 
myTimers.Add(timer)   
_waithandles[1] = new ManualResetEvent(false); 
// etc, and so on for all timers 

// then in each method that gets executed by the timer 
// simply set ManualReset event to signalled that will unblock it. 
private void MyExecute() 
{ 
    // do all my logic then when done signal the manual reset event 
    _waithandles[0].Set(); 
} 

// In your main before exiting, this will cause the main thread to wait 
// until all ManualResetEvents are set to signalled 
WaitHandle.WaitAll(_waithandles);  

Если вы только хотели ждать в ожидании операции, чтобы закончить то просто изменить что-то вроде этого:

_waithandles[0] = new ManualResetEvent(true); // initial state set to non blocking. 

private void MyExecute() 
{ 
    _waithandles[0].Reset(); // set this waithandle to block.. 

    // do all my logic then when done signal the manual reset event 
    _waithandles[0].Set(); 
} 
3

Принятый ответ от Томека приятный, но неполный. Если функция Dispose возвращает false, это означает, что нет необходимости ждать завершения, так как поток уже завершен. Если вы попытаетесь подождать WaitHandle в таком случае, WaitAll никогда не вернется, поэтому вы создали себе функцию, которая произвольно зависает вашего приложения/потока.

Вот как это должно выглядеть:

void WaitUntilCompleted(List<Timer> myTimers) 
    { 
     List<WaitHandle> waitHnd = new List<WaitHandle>(); 
     foreach (var timer in myTimers) 
     { 
      WaitHandle h = new AutoResetEvent(false); 
      if (timer.Dispose(h)) 
      { 
       waitHnd.Add(h); 
      } 
     } 
     WaitHandle.WaitAll(waitHnd.ToArray()); 
    } 
+1

Disagree, Dispose вернет false только в том случае, если вы вызываете более одного раза на Таймер. Тот факт, что обратный вызов таймера завершен, не влияет на возвращаемое значение Dispose. – Tomek

+0

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

Смежные вопросы