System.Threading.Timer w C # wydaje się nie działać. Działa bardzo szybko co 3 sekundy


112

Mam obiekt zegarowy. Chcę, żeby było uruchamiane co minutę. W szczególności powinien uruchamiać OnCallBackmetodę i stać się nieaktywny, gdy OnCallBackmetoda jest uruchomiona. Po OnCallBackzakończeniu metody (a OnCallBack) restartuje licznik czasu.

Oto, co mam teraz:

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
}

Jednak wydaje się, że nie działa. Działa bardzo szybko co 3 sekundy. Nawet jeśli podniesiesz kropkę (1000 * 10). Wygląda na to, że przymyka oko1000 * 10

Co zrobiłem źle?


12
From Timer.Change: "Jeśli dueTime ma wartość zero (0), metoda wywołania zwrotnego jest wywoływana natychmiast.". Wygląda na to, że to dla mnie zero.
— Damien_The_Unbeliever

2
Tak, ale co z tego? jest też okres.
— Alan Coromano

10
A co, jeśli jest też okres? Zacytowane zdanie nie zawiera zastrzeżeń co do wartości okresu. Mówi tylko, że „jeśli ta wartość jest równa zero, natychmiast wywołam wywołanie zwrotne”.
— Damien_The_Unbeliever

3
Co ciekawe, jeśli ustawisz zarówno dueTime, jak i period na 0, licznik czasu będzie działał co sekundę i rozpocznie się natychmiast.
— Kelvin,

Odpowiedzi:


230

To nie jest prawidłowe użycie pliku System.Threading.Timer. Podczas tworzenia instancji Timera prawie zawsze należy wykonać następujące czynności:

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

To poinstruuje timer, aby tykał tylko raz po upływie interwału. Następnie w funkcji oddzwaniania zmieniasz licznik czasu po zakończeniu pracy, a nie wcześniej. Przykład:

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

Dlatego nie ma potrzeby stosowania mechanizmów blokujących, ponieważ nie ma współbieżności. Timer uruchomi następne wywołanie zwrotne po upływie następnego interwału + czas długotrwałej operacji.

Jeśli chcesz uruchomić stoper z dokładnością do N milisekund, proponuję zmierzyć czas długotrwałej operacji za pomocą Stopwatch, a następnie odpowiednio wywołać metodę Change:

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

I zdecydowanie zachęcić ktoś robi .NET i wykorzystuje CLR, który nie czytać książki Jeffreya Richtera - CLR via C # , aby przeczytać to jak najszybciej. Liczniki czasu i pule wątków są tam szczegółowo wyjaśnione.


6
Nie zgadzam się z tym private void Callback( Object state ) { // Long running operation _timer.Change( TIME_INTERVAL_IN_MILLISECONDS, Timeout.Infinite ); }. Callbackmoże zostać wywołana ponownie przed zakończeniem operacji.
— Alan Coromano

2
Chodziło mi o to, że Long running operationmoże to zająć znacznie więcej czasu TIME_INTERVAL_IN_MILLISECONDS. Co by się wtedy stało?
— Alan Coromano

32
Oddzwonienie nie zostanie ponownie wywołane, o to chodzi. Dlatego jako drugi parametr przekazujemy Timeout.Infinite. Zasadniczo oznacza to, że nie zaznaczaj ponownie licznika czasu. Następnie zmień harmonogram, aby zaznaczyć po zakończeniu operacji.
— Ivan Zlatanov

Początkujący w wątkach tutaj - czy myślisz, że jest to możliwe ThreadPool, jeśli zdasz stoper? Myślę o scenariuszu, w którym nowy wątek jest odradzany, aby wykonać zadanie w określonych odstępach czasu - a po ukończeniu jest przenoszony do puli wątków.
— jedd.ahyoung

2
System.Threading.Timer to licznik czasu puli wątków, który wykonuje wywołania zwrotne w puli wątków, a nie w dedykowanym wątku. Gdy czasomierz zakończy procedurę wywołania zwrotnego, wątek, który wykonał wywołanie zwrotne, wraca do puli.
— Ivan Zlatanov

14

Nie ma potrzeby zatrzymywania timera, zobacz fajne rozwiązanie z tego postu :

„Możesz pozwolić zegarowi na kontynuowanie wywoływania metody wywołania zwrotnego, ale opakuj kod, który nie jest ponownie wprowadzany, w Monitor.TryEnter / Exit. W takim przypadku nie ma potrzeby zatrzymywania / restartowania licznika czasu; nakładające się wywołania nie uzyskają blokady i nie powrócą natychmiast.”

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

to nie w moim przypadku. Muszę dokładnie zatrzymać stoper.
— Alan Coromano

Czy próbujesz uniemożliwić nawiązanie połączenia zwrotnego więcej niż jeden raz? Jeśli nie, co próbujesz osiągnąć?
— Ivan Leonenko

1. Uniemożliwienie wchodzenia do oddzwaniania więcej niż jeden raz. 2. Zapobiegaj wykonywaniu zbyt wielu razy.
— Alan Coromano

Dokładnie to robi. # 2 nie jest zbyt dużym narzutem, o ile zwraca zaraz po instrukcji if, jeśli obiekt jest zablokowany, szczególnie jeśli masz tak duży interwał.
— Ivan Leonenko

1
Nie gwarantuje to, że kod zostanie wywołany nie wcześniej niż <interval> po ostatnim wykonaniu (nowy takt timera może zostać uruchomiony w mikrosekundę po zwolnieniu blokady przez poprzedni tik). Zależy to od tego, czy jest to wymóg ścisły, czy nie (nie do końca wynika z opisu problemu).
— Marco Mp

9

Czy używanie jest System.Threading.Timerobowiązkowe?

Jeśli nie, System.Timers.Timerma przydatne Start()i Stop()metody (i AutoResetwłaściwość, którą możesz ustawić na false, aby Stop()nie było potrzebne i po prostu wywołujesz Start()po wykonaniu).


3
Tak, ale może to być prawdziwy wymóg lub po prostu tak się stało, że wybrano timer, ponieważ jest najczęściej używany. Niestety .NET ma mnóstwo obiektów czasowych, nakładających się na siebie w 90%, ale wciąż (czasami subtelnie) różnych. Oczywiście, jeśli jest to wymagane, to rozwiązanie w ogóle nie ma zastosowania.
— Marco Mp

2
Zgodnie z dokumentacją : Klasa Systems.Timer jest dostępna tylko w .NET Framework. Nie jest uwzględniony w bibliotece .NET Standard i nie jest dostępny na innych platformach, takich jak .NET Core lub Universal Windows Platform. Na tych platformach, a także w celu zapewnienia przenośności na wszystkich platformach .NET, należy zamiast tego użyć klasy System.Threading.Timer.
— NotAgain mówi Przywróć Monikę

3

Po prostu zrobiłbym:

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
  }

I zignoruj ​​parametr okresu, ponieważ próbujesz samodzielnie kontrolować okresy.


Oryginalny kod działa tak szybko, jak to możliwe, skoro trzymasz określając 0dla dueTimeparametru. Od Timer.Change:

Jeśli dueTime ma wartość zero (0), metoda wywołania zwrotnego jest wywoływana natychmiast.


2
Czy konieczne jest pozbycie się timera? Dlaczego nie używasz Change()metody?
— Alan Coromano

21
Utylizacja timera za każdym razem jest absolutnie niepotrzebna i niewłaściwa.
— 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)

jeśli lubisz użyć jakiejś pętli w środku ...

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.
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.