System.Threading.Timer in C # sembra non funzionare. Funziona molto velocemente ogni 3 secondi


112

Ho un oggetto timer. Voglio che venga eseguito ogni minuto. In particolare, dovrebbe eseguire un OnCallBackmetodo e diventare inattivo mentre un OnCallBackmetodo è in esecuzione. Una volta che un OnCallBackmetodo finisce, (a OnCallBack) riavvia un timer.

Ecco cosa ho adesso:

private static Timer timer;

private static void Main()
{
    timer = new Timer(_ => OnCallBack(), null, 0, 1000 * 10); //every 10 seconds
    Console.ReadLine();
}

private static void OnCallBack()
{
    timer.Change(Timeout.Infinite, Timeout.Infinite); //stops the timer
    Thread.Sleep(3000); //doing some long operation
    timer.Change(0, 1000 * 10);  //restarts the timer
}

Tuttavia, sembra che non funzioni. Funziona molto velocemente ogni 3 secondi. Anche quando si alza un punto (1000 * 10). Sembra che chiuda un occhio1000 * 10

Cos'ho fatto di sbagliato?


12
Da Timer.Change: "Se dueTime è zero (0), il metodo di callback viene richiamato immediatamente.". A me sembra zero.
Damien_The_Unbeliever

2
Sì, ma allora cosa? c'è anche un periodo.
Alan Coromano

10
E se ci fosse anche il ciclo? La frase citata non rivendica il valore del periodo. Dice solo "se questo valore è zero, richiamerò immediatamente la richiamata".
Damien_The_Unbeliever

3
È interessante notare che se imposti dueTime e period su 0, il timer verrà eseguito ogni secondo e inizierà immediatamente.
Kelvin

Risposte:


230

Questo non è l'utilizzo corretto di System.Threading.Timer. Quando installi il timer, dovresti quasi sempre fare quanto segue:

_timer = new Timer( Callback, null, TIME_INTERVAL_IN_MILLISECONDS, Timeout.Infinite );

Questo indicherà al timer di spuntare solo una volta quando l'intervallo è trascorso. Quindi nella funzione di richiamata si modifica il timer una volta che il lavoro è stato completato, non prima. Esempio:

private void Callback( Object state )
{
    // Long running operation
   _timer.Change( TIME_INTERVAL_IN_MILLISECONDS, Timeout.Infinite );
}

Quindi non c'è bisogno di meccanismi di blocco perché non c'è concorrenza. Il timer attiverà la richiamata successiva allo scadere dell'intervallo successivo + il tempo dell'operazione di lunga durata.

Se devi far funzionare il tuo timer esattamente a N millisecondi, allora ti suggerisco di misurare il tempo dell'operazione di lunga durata usando Stopwatch e quindi chiamare il metodo Change in modo appropriato:

private void Callback( Object state )
{
   Stopwatch watch = new Stopwatch();

   watch.Start();
   // Long running operation

   _timer.Change( Math.Max( 0, TIME_INTERVAL_IN_MILLISECONDS - watch.ElapsedMilliseconds ), Timeout.Infinite );
}

Ho fortemente Incoraggio chiunque fare .NET e sta utilizzando il CLR che non ha letto il libro di Jeffrey Richter - CLR via C # , per leggere è il più presto possibile. I timer e i pool di thread sono spiegati in grande dettaglio qui.


6
Non sono d'accordo con quello private void Callback( Object state ) { // Long running operation _timer.Change( TIME_INTERVAL_IN_MILLISECONDS, Timeout.Infinite ); }. Callbackpotrebbe essere richiamato prima del completamento di un'operazione.
Alan Coromano

2
Quello che volevo dire era che Long running operationpotrebbe volerci molto più tempo allora TIME_INTERVAL_IN_MILLISECONDS. Cosa succederebbe allora?
Alan Coromano

31
La richiamata non verrà più richiamata, questo è il punto. Questo è il motivo per cui passiamo Timeout.Infinite come secondo parametro. Questo significa fondamentalmente non spuntare di nuovo per il timer. Quindi riprogramma per spuntare dopo aver completato l'operazione.
Ivan Zlatanov

Newbie to threading qui: pensi che sia possibile farlo con un ThreadPool, se passi il timer? Sto pensando a uno scenario in cui viene generato un nuovo thread per eseguire un lavoro a un certo intervallo e quindi relegato al pool di thread una volta completato.
jedd.ahyoung

2
Il System.Threading.Timer è un timer del pool di thread, che esegue i suoi callback sul pool di thread, non un thread dedicato. Dopo che il timer ha completato la routine di callback, il thread che ha eseguito il callback torna nel pool.
Ivan Zlatanov

14

Non è necessario fermare il timer, vedi bella soluzione da questo post :

"Potresti lasciare che il timer continui ad attivare il metodo di callback ma racchiudi il tuo codice non rientrante in un Monitor.TryEnter / Exit. In tal caso non è necessario arrestare / riavviare il timer; le chiamate sovrapposte non acquisiranno il blocco e torneranno immediatamente."

private void CreatorLoop(object state) 
 {
   if (Monitor.TryEnter(lockObject))
   {
     try
     {
       // Work here
     }
     finally
     {
       Monitor.Exit(lockObject);
     }
   }
 }

non è per il mio caso. Devo fermare esattamente il timer.
Alan Coromano

Stai cercando di impedire l'inserimento della richiamata più di una volta? Se no, cosa stai cercando di ottenere?
Ivan Leonenko

1. Impedire di accedere alla richiamata più di una volta. 2. Impedire l'esecuzione di troppe volte.
Alan Coromano

Questo è esattamente quello che fa. # 2 non è molto sovraccarico fintanto che ritorna subito dopo l'istruzione if se l'oggetto è bloccato, specialmente se hai un intervallo così grande.
Ivan Leonenko

1
Ciò non garantisce che il codice venga chiamato non meno di <interval> dopo l'ultima esecuzione (un nuovo tick del timer potrebbe essere attivato un microsecondo dopo che un tick precedente ha rilasciato il blocco). Dipende se questo è un requisito rigoroso o meno (non del tutto chiaro dalla descrizione del problema).
Marco Mp

9

L'uso è System.Threading.Timerobbligatorio?

In caso contrario, System.Timers.Timerha a portata di mano Start()e Stop()metodi (e una AutoResetproprietà che puoi impostare su false, in modo che Stop()non sia necessario e tu chiami semplicemente Start()dopo l'esecuzione).


3
Sì, ma potrebbe essere una vera esigenza, oppure è capitato che il timer sia stato scelto perché è quello più utilizzato. Purtroppo .NET ha tonnellate di oggetti timer, sovrapposti per il 90% ma ancora (a volte sottilmente) diversi. Ovviamente, se è un requisito, questa soluzione non si applica affatto.
Marco Mp

2
Come da documentazione : la classe Systems.Timer è disponibile solo in .NET Framework. Non è incluso nella libreria .NET Standard e non è disponibile su altre piattaforme, come .NET Core o Universal Windows Platform. Su queste piattaforme, oltre che per la portabilità su tutte le piattaforme .NET, è necessario utilizzare invece la classe System.Threading.Timer.
NotAgain dice Reinstate Monica

3

Vorrei solo fare:

private static Timer timer;
 private static void Main()
 {
   timer = new Timer(_ => OnCallBack(), null, 1000 * 10,Timeout.Infinite); //in 10 seconds
   Console.ReadLine();
 }

  private static void OnCallBack()
  {
    timer.Dispose();
    Thread.Sleep(3000); //doing some long operation
    timer = new Timer(_ => OnCallBack(), null, 1000 * 10,Timeout.Infinite); //in 10 seconds
  }

E ignora il parametro period, poiché stai tentando di controllare tu stesso la periodica.


Il codice originale viene eseguito il più velocemente possibile, poiché continui a specificare 0il dueTimeparametro. Da Timer.Change:

Se dueTime è zero (0), il metodo di callback viene richiamato immediatamente.


2
È necessario smaltire il timer? Perché non usi il Change()metodo?
Alan Coromano

21
Smaltire il timer ogni volta è assolutamente inutile e sbagliato.
Ivan Zlatanov

0
 var span = TimeSpan.FromMinutes(2);
 var t = Task.Factory.StartNew(async delegate / () =>
   {
        this.SomeAsync();
        await Task.Delay(span, source.Token);
  }, source.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);

source.Cancel(true/or not);

// or use ThreadPool(whit defaul options thread) like this
Task.Start(()=>{...}), source.Token)

se ti piace usare un filo ad anello all'interno ...

public async void RunForestRun(CancellationToken token)
{
  var t = await Task.Factory.StartNew(async delegate
   {
       while (true)
       {
           await Task.Delay(TimeSpan.FromSeconds(1), token)
                 .ContinueWith(task => { Console.WriteLine("End delay"); });
           this.PrintConsole(1);
        }
    }, token) // drop thread options to default values;
}

// And somewhere there
source.Cancel();
//or
token.ThrowIfCancellationRequested(); // try/ catch block requred.
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.