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.