web-dev-qa-db-fra.com

System.Threading.Timer en C #, il semble ne pas fonctionner. Il court très vite toutes les 3 secondes

J'ai un objet timer. Je veux qu'il soit exécuté chaque minute. Plus précisément, il doit exécuter une méthode OnCallBack et devient inactif pendant l'exécution d'une méthode OnCallBack. Une fois qu'une méthode OnCallBack est terminée, elle (une OnCallBack) redémarre un minuteur.

Voici ce que j'ai en ce moment:

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
}

Cependant, cela ne semble pas fonctionner. Il court très vite toutes les 3 secondes. Même quand si augmenter une période (1000 * 10). Il semble que cela ferme les yeux sur 1000 * 10

Qu'ai-je fait de mal?

102
Alan Coromano

Ce n'est pas l'utilisation correcte de System.Threading.Timer. Lorsque vous instanciez le minuteur, vous devriez presque toujours procéder comme suit:

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

Cela demandera à la minuterie de cocher une seule fois lorsque l'intervalle est écoulé. Ensuite, dans votre fonction de rappel, vous modifiez la minuterie une fois le travail terminé, pas avant. Exemple:

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

Il n’ya donc pas besoin de mécanismes de verrouillage car il n’ya pas de concurrence. La minuterie déclenchera le prochain rappel une fois l'intervalle suivant écoulé + la durée de la longue opération.

Si vous devez exécuter votre minuterie à exactement N millisecondes, je vous suggère de mesurer le temps de la longue opération à l'aide de Chronomètre, puis appelez la méthode Change en conséquence:

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 );
}

Je fortement encourage ceux qui font .NET et utilisent le CLR qui n'a pas lu le livre de Jeffrey Richter - CLR via C # , à lire dès que possible. Les timers et les pools de threads y sont expliqués en détail.

210
Ivan Zlatanov

Il n'est pas nécessaire d'arrêter le chronomètre, voir la solution de Nice dans cet article :

"Vous pouvez laisser le minuteur continuer à déclencher la méthode de rappel mais enrouler votre code non réentrant dans un moniteur.

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

L'utilisation de System.Threading.Timer est-elle obligatoire?

Sinon, System.Timers.Timer a des méthodes pratiques Start () et Stop () (et une propriété AutoReset que vous pouvez définir sur false, de sorte que Stop () n'est pas nécessaire et que vous appelez simplement Start () après son exécution).

8
Marco Mp

Je voudrais juste faire:

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
  }

Et ignorez le paramètre de période, puisque vous essayez de contrôler vous-même la période.


Votre code d'origine fonctionne aussi rapidement que possible, car vous continuez à spécifier 0 pour le paramètre dueTime. De Timer.Change :

Si dueTime est à zéro (0), la méthode de rappel est immédiatement appelée.

4
 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)

si vous aimez utiliser un fil de boucle à l'intérieur ...

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.
0
Umka