2011-01-26 12 views
6

Con riferimento a questo preventivo da MSDN sul System.Timers.Timer:Sincronizzazione di un metodo trascorso Timers.Timer quando si arresta

L'evento Timer.Elapsed è sollevata su un filo ThreadPool, quindi l'evento il metodo di gestione può essere eseguito su un thread contemporaneamente a una chiamata a mentre il metodo Timer.Stop viene eseguito su un altro thread . Ciò potrebbe causare l'evento Elapsed che viene generato dopo che è stato chiamato il metodo Stop . Questa condizione di competizione non può essere prevenuta semplicemente confrontando la proprietà SignalTime con il momento in cui il metodo Stop è chiamato, perché il metodo di gestione degli eventi potrebbe essere già in esecuzione quando il metodo fermata si chiama, o potrebbe iniziare l'esecuzione tra il momento quando viene chiamato il metodo Stop e il momento quando viene salvato il tempo di arresto. Se è fondamentale per evitare che il filo che chiama il metodo Stop da procedimento mentre il metodo di gestione degli eventi è ancora in esecuzione, utilizzare un più robusto meccanismo di sincronizzazione come come la classe Monitor o il metodo CompareExchange. Il codice che utilizza il metodo CompareExchange può essere trovato nell'esempio per il metodo Timer.Stop .

chiunque può dare un esempio di un "meccanismo di sincronizzazione robusta come la classe Monitor" per spiegare cosa significa esattamente?

Sto pensando che significa usare un blocco in qualche modo, ma non sono sicuro di come lo implementeresti.

risposta

11

Interruzione di uno System.Timers.Timer affidabile è davvero un grande sforzo. Il problema più serio è che i thread del threadpool che utilizza per chiamare l'evento Elapsed possono eseguire il backup a causa dell'algoritmo dello scheduler del threadpool. Avere un paio di chiamate di backup non è insolito, avere centinaia è tecnicamente possibile.

Sono necessarie due sincronizzazioni, una per assicurarsi di arrestare il timer solo quando non è in esecuzione alcun gestore eventi Elapsed, un altro per garantire che questi thread TP di backup non danneggino. In questo modo:

System.Timers.Timer timer = new System.Timers.Timer(); 
    object locker = new object(); 
    ManualResetEvent timerDead = new ManualResetEvent(false); 

    private void Timer_Elapsed(object sender, ElapsedEventArgs e) { 
     lock (locker) { 
      if (timerDead.WaitOne(0)) return; 
      // etc... 
     } 
    } 

    private void StopTimer() { 
     lock (locker) { 
      timerDead.Set(); 
      timer.Stop(); 
     } 
    } 

Prendere in considerazione l'impostazione della proprietà AutoReset su false. È un altro modo fragile, l'evento Elapsed viene chiamato da un metodo .NET interno che cattura Exception. Molto brutto, il tuo codice timer smette di funzionare senza alcuna diagnostica. Non conosco la storia, ma ci doveva essere un altro team di MSFT che ha soffiato e gonfiato a questo casino e ha scritto System.Threading.Timer. Altamente raccomandato.

+1

Wow, i timer sono davvero bestie insidiose. : o – Andy

+0

Sì, questo cade quando sei sull'aereo che vola a casa. Huff e puff, stress testate l'inferno prima di salire a bordo. –

+0

Avendo riflettuto un po ', sembra che crei un Threading.Timer dietro le quinte, e il callback in effetti annulla le eccezioni. L'implementazione del threading di base.Timer da solo sembra essere molto più pulito, quindi avrò una lettura su questo. – Andy

0

Questo è ciò che suggerisce.

Monitor è la classe utilizzata dal compilatore C# per un'istruzione lock.

Detto questo, quanto sopra è solo un problema se si tratta di un problema nella vostra situazione. L'intera istruzione si traduce fondamentalmente in "È possibile ottenere un evento timer che si verifica subito dopo aver chiamato Stop(). Se questo è un problema, è necessario gestirlo". A seconda di cosa sta facendo il tuo timer, potrebbe essere un problema, oppure no.

Se si tratta di un problema, la pagina Timer.Stop mostra un modo efficace (utilizzando Interlocked.CompareExchange) per gestirlo. Basta copiare il codice dal campione e modificare se necessario.

0

Prova:

lock(timer) { 
timer.Stop(); 
} 
+0

Questo non avrà alcun effetto, almeno non senza un po 'di altro codice ... Inoltre, sarebbe una scelta scadente di meccanismi di sincronizzazione. –

0

Ecco un modo molto semplice per prevenire questa condizione si verifichi gara:

private object _lock = new object(); 
private Timer _timer; // init somewhere else 

public void StopTheTimer() 
{ 
    lock (_lock) 
    { 
     _timer.Stop(); 
    } 
} 

void elapsed(...) 
{ 
    lock (_lock) 
    { 
     if (_timer.Enabled) // prevent event after Stop() is called 
     { 
      // do whatever you do in the timer event 
     } 
    } 
} 
+0

Questo in realtà non impedirà la condizione di gara menzionata. Il timerTick può avvenire simultaneamente con la chiamata a _timer.Stop() - e comunque essere attivato successivamente (a quel punto, il blocco verrà rilasciato). –

+0

Ho pensato dal testo citato che l'obiettivo era impedire che il timer venisse arrestato se il metodo di gestione degli eventi era nel mezzo dell'esecuzione. Il mio codice lo realizzerebbe (penso). – MusiGenesis

+0

"Ciò potrebbe causare l'evento Elapsed che viene generato dopo che è stato chiamato il metodo Stop." - che può ancora succedere con questa opzione. - e il corpo del metodo trascorso verrà comunque eseguito completamente, poiché non sta controllando per vedere se il timer è stato interrotto ... –

0

Sembra che il timer non sia thread-safe. È necessario mantenere tutte le chiamate su di esso in sincronizzazione tramite blocco. lock (object) {} è in realtà solo una mano breve per una semplice chiamata al monitor.

Problemi correlati