Code Review: Timery

Załóżmy, że mamy timer, który co jakiś próbuje połączyć się np. z usługą:

internal static class Sample
{
   public static void Main()
   {
       using (Timer timer = new Timer(Run, null,0,1000))
       {
           Thread.Sleep(500000);
       }
   }
   private static void Run(Object state)
   {
       Console.WriteLine("Operacja, ktora moze potrwac czasami nawet kilka minut.");
   }
}

Powyższy kod w wielu sytuacjach jest poprawny. Należy jednak mieć na uwadze, że operacje takie jak połączenie z bazą danych czy usługą mogą potrwać bardzo długo. Z tego względu, może okazać się, że metoda Run będzie wywołana kilka razy w tym samym czasie. Łatwo przetestować taki scenariusz, dodając Thread.Sleep:

internal static class Sample
{
   public static void Main()
   {
       using (Timer timer = new Timer(Run, null,0,1000))
       {
           Thread.Sleep(500000);
       }
   }
   private static void Run(Object state)
   {
       Console.WriteLine("Przed");
       Thread.Sleep(3000);
       Console.WriteLine("Po");
   }
}

W powyższym przypadku, Run i tak będzie wywoływany co każdą sekundę, skutkując wykonywaniem jednoczesnym kilku Run. Taki przypadek nie jest thread-safe i czasami po prostu zachowanie jest niepożądane – po co łączyć się dwa razy do bazy danych aby odświeżyć jakąś informacje? Jeśli jeden wątek nie może tego zrobić, to nie ma sensu wykonywanie dokładnie tego  samego w drugim. Lepszym podejściem jest uruchomienie Run tylko raz i potem wywoływaniem w Run za każdym razem funkcji Change:

internal static class Sample
{
   private static Timer _timer;
   public static void Main()
   {
       using (_timer = new Timer(Run, null, 1000, Timeout.Infinite))
       {
           Thread.Sleep(500000);
       }
   }
   private static void Run(Object state)
   {
       Console.WriteLine("Przed");
       Thread.Sleep(3000);
       Console.WriteLine("Po");
       _timer.Change(1000, Timeout.Infinite);
   }
}

Pierwsza różnica to utworzenie instancji Timer – zamiast parametru period, teraz ustawiamy dueTime, czyli opóźnienie z jakim ma zostać uruchomiony Timer. Następnie po każdym wykonaniu kodu w Run, wywołujemy Change ponownie z dueTime równym 1000.

4 thoughts on “Code Review: Timery”

  1. Przeczytałem kilka razy ten wpis i nie mogłem go w pełni zrozumieć. Skoro ‘period’ wynoszący 1000 powoduje odpalanie callbacka co 1000 ms, to 0 powinno oznaczać 0 ms – od razu. Wbrew pozorom tak nie jest 😉 Dopiero reflector wyjaśnił mi sytuację:

    timer.m_period = (int) period == 0 ? uint.MaxValue : period;

    Czyli 0 ustawia maksymalny, możliwy czas między powtórzeniami. Uważam, że lepiej jest napisać tak:

    new Timer(Run, null, 1000, Timeout.Infinite)

    Taki zapis mówi więcej osobie czytającej kod oraz pokrywa się z dokumentacją klasy Timer.

  2. Jeśli komuś nie chce się sprawdzać w VS, to Timeout.Infinite = -1.

    Btw, dodatkowo:
    _timer.Change(1000, Timeout.Infinite);

Leave a Reply

Your email address will not be published.