I timer C # trascorrono in un thread separato?


97

Un System.Timers.Timer trascorre su un thread separato rispetto al thread che lo ha creato?

Diciamo che ho una lezione con un timer che si attiva ogni 5 secondi. Quando il timer si attiva, nel metodo trascorso, alcuni oggetti vengono modificati. Diciamo che ci vuole molto tempo per modificare questo oggetto, come 10 secondi. È possibile che incorrerò in collisioni di thread in questo scenario?


Ciò potrebbe causare problemi. Si noti che, in generale, i thread del pool di thread non sono progettati per processi a esecuzione prolungata.
Greg D,

Mi stavo imbattendo in questo, testando con un servizio Windows. Ciò che ha funzionato per me è stato disabilitare il timer come prima istruzione nell'evento OnTimer, eseguire le mie attività, quindi abilitare il timer alla fine. Questo ha funzionato in modo affidabile in un ambiente di produzione per qualche tempo.
Steve,

Risposte:


60

Per System.Timers.Timer :

Vedi la risposta di Brian Gideon di seguito

Per System.Threading.Timer :

La documentazione di MSDN sui timer afferma:

La classe System.Threading.Timer effettua callback su un thread ThreadPool e non utilizza affatto il modello di eventi.

Quindi effettivamente il timer scade su un thread diverso.


21
Vero, ma questa è una classe completamente diversa. L'OP ha chiesto informazioni sulla classe System.Timers.Timer.
Brian Gideon,

1
Oh, hai ragione. msdn.microsoft.com/en-us/library/system.timers.timer.aspx dice "L'evento Elapsed viene generato su un thread ThreadPool." Stessa conclusione da lì suppongo.
Joren,

6
Beh, sì, ma non è così semplice. Vedi la mia risposta.
Brian Gideon,

192

Dipende. Il System.Timers.Timerha due modi di funzionamento.

Se SynchronizingObjectè impostato su ISynchronizeInvokeun'istanza, l' Elapsedevento verrà eseguito sul thread che ospita l'oggetto di sincronizzazione. Di solito questi ISynchronizeInvokecasi non sono altro che semplici vecchi Controle Formcasi che tutti conosciamo. Quindi in quel caso l' Elapsedevento viene richiamato sul thread dell'interfaccia utente e si comporta in modo simile al System.Windows.Forms.Timer. In caso contrario, dipende davvero dall'istanza specifica ISynchronizeInvokeutilizzata.

Se SynchronizingObjectè null, l' Elapsedevento viene richiamato su un ThreadPoolthread e si comporta in modo simile al System.Threading.Timer. In effetti, utilizza effettivamente un System.Threading.Timerdietro le quinte ed esegue l'operazione di marshalling dopo aver ricevuto il callback del timer, se necessario.


4
Se vuoi che la richiamata del timer venga eseguita su un nuovo thread, dovresti usare System.Threading.Timero System.Timers.Timer?
CJ7

1
@ cj7: O uno può farlo.
Brian Gideon

e se ho una lista di tipo complesso (persona) e voglio avere un tempo dentro ogni persona? Ho bisogno che questo funzioni sullo stesso thread (tutte le persone), perché se chiama il metodo della prima persona, la seconda persona deve attendere fino a quando la prima termina l'evento trascorso. Posso farlo?
Leandro De Mello Fagundes

4
Tutti ... System.Timers.Timerhanno due modalità di funzionamento. Può essere eseguito su un thread del pool di thread assegnato in modo casuale OPPURE può essere eseguito su qualsiasi thread che ospita l' ISynchronizeInvokeistanza. Non so come renderlo più chiaro. System.Threading.Timerha poco (semmai) a che fare con la domanda originale.
Brian Gideon

@ LeandroDeMelloFagundes Non puoi usare lockper quello?
Ozkan

24

Ogni evento trascorso verrà attivato nello stesso thread a meno che un Elapsed precedente non sia ancora in esecuzione.

Quindi gestisce la collisione per te

prova a metterlo in una console

static void Main(string[] args)
{
    Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
    var timer = new Timer(1000);
    timer.Elapsed += timer_Elapsed;
    timer.Start();
    Console.ReadLine();
}

static void timer_Elapsed(object sender, ElapsedEventArgs e)
{
    Thread.Sleep(2000);
    Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
}

otterrai qualcosa di simile

10
6
12
6
12

dove 10 è il thread chiamante e 6 e 12 si attivano dall'evento trascorso bg. Se rimuovi Thread.Sleep (2000); otterrai qualcosa di simile

10
6
6
6
6

Poiché non ci sono collisioni.

Ma questo ti lascia ancora con un problema. se stai attivando l'evento ogni 5 secondi e ci vogliono 10 secondi per modificare, hai bisogno di un blocco per saltare alcune modifiche.


8
L'aggiunta di un timer.Stop()all'inizio del metodo dell'evento Elapsed e quindi di un timer.Start()alla fine del metodo dell'evento Elapsed eviterà la collisione dell'evento Elapsed.
Metro Smurf

4
Non è necessario inserire timer.Stop (), è sufficiente definire timer.AutoReset = false; quindi crei timer.Start () dopo aver elaborato l'evento. Penso che questo sia un modo migliore per evitare le collisioni.
João Antunes

17

Per System.Timers.Timer, su thread separato, se SynchronizingObject non è impostato.

    static System.Timers.Timer DummyTimer = null;

    static void Main(string[] args)
    {
        try
        {

            Console.WriteLine("Main Thread Id: " + System.Threading.Thread.CurrentThread.ManagedThreadId);

            DummyTimer = new System.Timers.Timer(1000 * 5); // 5 sec interval
            DummyTimer.Enabled = true;
            DummyTimer.Elapsed += new System.Timers.ElapsedEventHandler(OnDummyTimerFired);
            DummyTimer.AutoReset = true;

            DummyTimer.Start();

            Console.WriteLine("Hit any key to exit");
            Console.ReadLine();
        }
        catch (Exception Ex)
        {
            Console.WriteLine(Ex.Message);
        }

        return;
    }

    static void OnDummyTimerFired(object Sender, System.Timers.ElapsedEventArgs e)
    {
        Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);
        return;
    }

Output che vedresti se DummyTimer si attivasse con un intervallo di 5 secondi:

Main Thread Id: 9
   12
   12
   12
   12
   12
   ... 

Quindi, come visto, OnDummyTimerFired viene eseguito sul thread Workers.

No, ulteriore complicazione: se riduci l'intervallo a dire 10 ms,

Main Thread Id: 9
   11
   13
   12
   22
   17
   ... 

Questo perché se l'esecuzione precedente di OnDummyTimerFired non viene eseguita quando viene attivato il tick successivo, .NET creerebbe un nuovo thread per eseguire questo lavoro.

A complicare ulteriormente le cose, "La classe System.Timers.Timer fornisce un modo semplice per affrontare questo dilemma: espone una proprietà SynchronizingObject pubblica. L'impostazione di questa proprietà su un'istanza di un Windows Form (o un controllo su un Windows Form) garantirà che il codice nel gestore dell'evento Elapsed venga eseguito sullo stesso thread in cui è stata creata l'istanza di SynchronizingObject. "

http://msdn.microsoft.com/en-us/magazine/cc164015.aspx#S2


Questo è un buon esempio. Non lo sapevo fino ad ora e ho bisogno di impostare alcune serrature qua e là. Perfetto.
Olaru Mircea

E se volessi che il timer attivi l'evento Elapsed nel thread principale di un'applicazione console?
jacktric

13

Se l'evento trascorso richiede più tempo dell'intervallo, creerà un altro thread per generare l'evento trascorso. Ma c'è una soluzione alternativa per questo

static void timer_Elapsed(object sender, ElapsedEventArgs e)    
{     
   try
   {
      timer.Stop(); 
      Thread.Sleep(2000);        
      Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);    
   }
   finally
   {
     timer.Start();
   }
}

+1 Risposta semplice e pulita, ma non sempre funzionerà. Piuttosto si dovrebbe usare la proprietà lock o SynchronizingObject del timer.
RollerCosta
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.