System.Threading.Timer in C # scheint nicht zu funktionieren. Es läuft alle 3 Sekunden sehr schnell

112

Ich habe ein Timer-Objekt. Ich möchte, dass es jede Minute ausgeführt wird. Insbesondere sollte es eine OnCallBackMethode ausführen und wird inaktiv, während eine OnCallBackMethode ausgeführt wird. Sobald eine OnCallBackMethode beendet ist, OnCallBackstartet sie (a ) einen Timer neu.

Folgendes habe ich gerade:

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
}

Es scheint jedoch nicht zu funktionieren. Es läuft alle 3 Sekunden sehr schnell. Auch wenn Sie einen Zeitraum erhöhen (1000 * 10). Es scheint, als würde es ein Auge zudrücken1000 * 10

Was habe ich falsch gemacht?

Alan Coromano
quelle
12
Von Timer.Change: "Wenn dueTime Null (0) ist, wird die Rückrufmethode sofort aufgerufen." Sieht so aus, als wäre es Null für mich.
Damien_The_Unbeliever
2
Ja, aber na und? Es gibt auch eine Periode.
Alan Coromano
10
Was ist, wenn es auch eine Periode gibt? Der zitierte Satz erhebt keinen Anspruch auf den Periodenwert. Es heißt nur "Wenn dieser Wert Null ist, werde ich den Rückruf sofort aufrufen".
Damien_The_Unbeliever
3
Interessanterweise läuft der Timer jede Sekunde und startet sofort, wenn Sie sowohl dueTime als auch period auf 0 setzen.
Kelvin

Antworten:

230

Dies ist nicht die korrekte Verwendung des System.Threading.Timer. Wenn Sie den Timer instanziieren, sollten Sie fast immer Folgendes tun:

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

Dadurch wird der Timer angewiesen, nach Ablauf des Intervalls nur einmal zu ticken. Dann ändern Sie in Ihrer Rückruffunktion den Timer, sobald die Arbeit abgeschlossen ist, nicht vorher. Beispiel:

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

Daher sind keine Sperrmechanismen erforderlich, da keine Parallelität besteht. Der Timer löst den nächsten Rückruf nach Ablauf des nächsten Intervalls + der Zeit des lang laufenden Vorgangs aus.

Wenn Sie Ihren Timer mit genau N Millisekunden ausführen müssen, empfehlen wir Ihnen, die Zeit des Langzeitbetriebs mit der Stoppuhr zu messen und dann die Änderungsmethode entsprechend aufzurufen:

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

Ich stark fördern .NET jemand tun , und wird unter Verwendung der CLR , die nicht Jeffrey Richters Buch gelesen hat - CLR via C # , zu lesen ist , so bald wie möglich. Timer und Thread-Pools werden dort ausführlich erklärt.

Ivan Zlatanov
quelle
6
Dem stimme ich nicht zu private void Callback( Object state ) { // Long running operation _timer.Change( TIME_INTERVAL_IN_MILLISECONDS, Timeout.Infinite ); }. Callbackwird möglicherweise erneut aufgerufen, bevor eine Operation abgeschlossen ist.
Alan Coromano
2
Was ich damit meinte war, dass Long running operationdas dann viel länger dauern könnte TIME_INTERVAL_IN_MILLISECONDS. Was würde dann passieren?
Alan Coromano
31
Rückruf wird nicht erneut aufgerufen, das ist der Punkt. Aus diesem Grund übergeben wir Timeout.Infinite als zweiten Parameter. Dies bedeutet im Grunde, dass Sie den Timer nicht erneut ankreuzen müssen. Planen Sie dann neu, um zu ticken, nachdem wir die Operation abgeschlossen haben.
Ivan Zlatanov
Neuling beim Threading hier - denkst du, dass dies mit einem möglich ist ThreadPool, wenn du den Timer übergibst? Ich denke an ein Szenario, in dem ein neuer Thread erzeugt wird, um in einem bestimmten Intervall einen Job auszuführen - und dann nach Abschluss in den Thread-Pool verbannt wird.
jedd.ahyoung
2
Der System.Threading.Timer ist ein Thread-Pool-Timer, der seine Rückrufe im Thread-Pool ausführt, nicht in einem dedizierten Thread. Nachdem der Timer die Rückrufroutine abgeschlossen hat, kehrt der Thread, der den Rückruf ausgeführt hat, in den Pool zurück.
Ivan Zlatanov
14

Es ist nicht notwendig, den Timer anzuhalten, siehe nette Lösung aus diesem Beitrag :

"Sie können den Timer weiterhin die Rückrufmethode auslösen lassen, aber Ihren nicht wiedereintretenden Code in einen Monitor einbinden. TryEnter / Exit. In diesem Fall muss der Timer nicht gestoppt / neu gestartet werden. Überlappende Anrufe erhalten die Sperre nicht und kehren nicht sofort zurück."

private void CreatorLoop(object state) 
 {
   if (Monitor.TryEnter(lockObject))
   {
     try
     {
       // Work here
     }
     finally
     {
       Monitor.Exit(lockObject);
     }
   }
 }
Ivan Leonenko
quelle
Es ist nicht für meinen Fall. Ich muss den Timer genau stoppen.
Alan Coromano
Versuchen Sie zu verhindern, dass Sie mehr als einmal einen Rückruf eingeben? Wenn nein, was versuchst du zu erreichen?
Ivan Leonenko
1. Verhindern Sie, dass Sie mehrmals zum Rückruf zurückkehren. 2. Verhindern Sie zu viele Ausführungen.
Alan Coromano
Genau das macht es. # 2 ist nicht viel Aufwand, solange es direkt nach der if-Anweisung zurückkehrt, wenn das Objekt gesperrt ist, insbesondere wenn Sie ein so großes Intervall haben.
Ivan Leonenko
1
Dies garantiert nicht, dass der Code nach der letzten Ausführung mindestens <Intervall> aufgerufen wird (ein neues Häkchen des Timers kann eine Mikrosekunde ausgelöst werden, nachdem ein vorheriges Häkchen die Sperre aufgehoben hat). Es hängt davon ab, ob dies eine strenge Anforderung ist oder nicht (nicht ganz klar aus der Problembeschreibung).
Marco Mp
9

Ist die Verwendung System.Threading.Timerobligatorisch?

Wenn nicht, System.Timers.Timerhat handliche Start()und Stop()Methoden (und eine AutoResetEigenschaft, die Sie auf false setzen können, so dass die Stop()nicht benötigt wird und Sie einfach Start()nach der Ausführung aufrufen ).

Marco Mp
quelle
3
Ja, aber es könnte eine echte Anforderung sein, oder es ist einfach passiert, dass der Timer ausgewählt wurde, weil er am häufigsten verwendet wird. Leider hat .NET Tonnen von Timer-Objekten, die sich zu 90% überlappen, sich aber (manchmal subtil) unterscheiden. Wenn dies erforderlich ist, gilt diese Lösung natürlich überhaupt nicht.
Marco Mp
2
Gemäß der Dokumentation : Die Systems.Timer Klasse ist nur in .NET Framework zur Verfügung. Es ist nicht in der .NET-Standardbibliothek enthalten und auf anderen Plattformen wie .NET Core oder der universellen Windows-Plattform
NotAgain sagt Reinstate Monica
3

Ich würde einfach tun:

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
  }

Ignorieren Sie den Parameter period, da Sie versuchen, die Periodizität selbst zu steuern.


Ihr ursprünglicher Code wird so schnell wie möglich ausgeführt, da Sie immer wieder 0den dueTimeParameter angeben. Von Timer.Change:

Wenn dueTime Null (0) ist, wird die Rückrufmethode sofort aufgerufen.

Damien_The_Unbeliever
quelle
2
Ist es notwendig, Timer zu entsorgen? Warum benutzt du keine Change()Methode?
Alan Coromano
21
Es ist absolut unnötig und falsch, den Timer jedes Mal zu entsorgen.
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)

Wenn du magst, benutze einen Schleifenfaden im Inneren ...

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