Najlepszy czasomierz do używania w usłudze Windows

108

Muszę stworzyć usługę systemu Windows, która będzie wykonywana co N okresu czasu.
Pytanie brzmi:
której kontrolki timera powinienem użyć: System.Timers.Timerczy System.Threading.Timerjednej? Czy to na coś wpływa?

Pytam, ponieważ słyszałem wiele dowodów na nieprawidłową pracę System.Timers.Timerw usługach Windows.
Dziękuję Ci.

shA.t
źródło

Odpowiedzi:

118

Oba System.Timers.Timeri System.Threading.Timerbędą działać dla usług.

Zegary, których chcesz unikać, to System.Web.UI.Timeri System.Windows.Forms.Timer, które są odpowiednie dla aplikacji ASP i WinForms. Korzystanie z nich spowoduje, że usługa załaduje dodatkowy zestaw, który nie jest tak naprawdę potrzebny dla typu budowanej aplikacji.

Użyj System.Timers.Timerjak w poniższym przykładzie (upewnij się, że używasz zmiennej poziomu klasy, aby zapobiec zbieraniu elementów bezużytecznych, zgodnie z odpowiedzią Tima Robinsona):

using System;
using System.Timers;

public class Timer1
{
    private static System.Timers.Timer aTimer;

    public static void Main()
    {
        // Normally, the timer is declared at the class level,
        // so that it stays in scope as long as it is needed.
        // If the timer is declared in a long-running method,  
        // KeepAlive must be used to prevent the JIT compiler 
        // from allowing aggressive garbage collection to occur 
        // before the method ends. (See end of method.)
        //System.Timers.Timer aTimer;

        // Create a timer with a ten second interval.
        aTimer = new System.Timers.Timer(10000);

        // Hook up the Elapsed event for the timer.
        aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);

        // Set the Interval to 2 seconds (2000 milliseconds).
        aTimer.Interval = 2000;
        aTimer.Enabled = true;

        Console.WriteLine("Press the Enter key to exit the program.");
        Console.ReadLine();

        // If the timer is declared in a long-running method, use
        // KeepAlive to prevent garbage collection from occurring
        // before the method ends.
        //GC.KeepAlive(aTimer);
    }

    // Specify what you want to happen when the Elapsed event is 
    // raised.
    private static void OnTimedEvent(object source, ElapsedEventArgs e)
    {
        Console.WriteLine("The Elapsed event was raised at {0}", e.SignalTime);
    }
}

/* This code example produces output similar to the following:

Press the Enter key to exit the program.
The Elapsed event was raised at 5/20/2007 8:42:27 PM
The Elapsed event was raised at 5/20/2007 8:42:29 PM
The Elapsed event was raised at 5/20/2007 8:42:31 PM
...
 */

Jeśli chcesz System.Threading.Timer, możesz użyć w następujący sposób:

using System;
using System.Threading;

class TimerExample
{
    static void Main()
    {
        AutoResetEvent autoEvent     = new AutoResetEvent(false);
        StatusChecker  statusChecker = new StatusChecker(10);

        // Create the delegate that invokes methods for the timer.
        TimerCallback timerDelegate = 
            new TimerCallback(statusChecker.CheckStatus);

        // Create a timer that signals the delegate to invoke 
        // CheckStatus after one second, and every 1/4 second 
        // thereafter.
        Console.WriteLine("{0} Creating timer.\n", 
            DateTime.Now.ToString("h:mm:ss.fff"));
        Timer stateTimer = 
                new Timer(timerDelegate, autoEvent, 1000, 250);

        // When autoEvent signals, change the period to every 
        // 1/2 second.
        autoEvent.WaitOne(5000, false);
        stateTimer.Change(0, 500);
        Console.WriteLine("\nChanging period.\n");

        // When autoEvent signals the second time, dispose of 
        // the timer.
        autoEvent.WaitOne(5000, false);
        stateTimer.Dispose();
        Console.WriteLine("\nDestroying timer.");
    }
}

class StatusChecker
{
    int invokeCount, maxCount;

    public StatusChecker(int count)
    {
        invokeCount  = 0;
        maxCount = count;
    }

    // This method is called by the timer delegate.
    public void CheckStatus(Object stateInfo)
    {
        AutoResetEvent autoEvent = (AutoResetEvent)stateInfo;
        Console.WriteLine("{0} Checking status {1,2}.", 
            DateTime.Now.ToString("h:mm:ss.fff"), 
            (++invokeCount).ToString());

        if(invokeCount == maxCount)
        {
            // Reset the counter and signal Main.
            invokeCount  = 0;
            autoEvent.Set();
        }
    }
}

Oba przykłady pochodzą ze stron MSDN.

Andrew Moore
źródło
1
Dlaczego sugerujesz: GC.KeepAlive (aTimer) ;, aTimer jest zmienną instancji w prawo, więc na przykład, jeśli jest to zmienna instancji formy, zawsze będzie do niej odwołanie, o ile istnieje forma, prawda?
giorgim
37

Nie używaj do tego usługi. Utwórz normalną aplikację i utwórz zaplanowane zadanie, aby ją uruchomić.

Jest to powszechnie stosowana najlepsza praktyka. Jon Galloway się ze mną zgadza. A może jest na odwrót. Tak czy inaczej, faktem jest, że nie jest najlepszym rozwiązaniem tworzenie usługi systemu Windows w celu wykonania sporadycznego zadania uruchamianego z timerem.

„Jeśli piszesz usługę systemu Windows, która uruchamia licznik czasu, powinieneś ponownie ocenić swoje rozwiązanie”.

–Jon Galloway, menedżer programu społecznościowego ASP.NET MVC, autor, superbohater na pół etatu

Społeczność
źródło
26
Jeśli Twoja usługa ma działać przez cały dzień, być może usługa ma sens, a nie zaplanowane zadanie. Lub posiadanie usługi może uprościć administrowanie i rejestrowanie dla grupy infrastruktury, która nie jest tak doświadczona jak zespół ds. Aplikacji. Jednak kwestionowanie założenia, że ​​usługa jest wymagana, jest całkowicie słuszne, a te negatywne oceny są niezasłużone. +1 do obu.
sfuqua
2
@mr Nonsense. Zaplanowane zadania są podstawową częścią systemu operacyjnego. Proszę zachować FUD dla siebie.
4
@MR: Nie jestem ewangelistą, jestem realistą. A rzeczywistość nauczyła mnie, że zaplanowane zadania nie są „bardzo obciążone”. W rzeczywistości to od ciebie jako osoby, która twierdzi, że ją popierasz. W przeciwnym razie wszystko, co robisz, to szerzenie strachu, niepewności i wątpliwości.
13
Nienawidzę tu brodzić w błocie, ale muszę trochę bronić MR. W mojej firmie działa kilka krytycznych aplikacji, które są aplikacjami konsoli systemu Windows. Użyłem Harmonogramu zadań systemu Windows do uruchomienia ich wszystkich. Co najmniej 5 razy mieliśmy teraz problem polegający na tym, że usługa harmonogramu „została w jakiś sposób zdezorientowana”. Zadania nie zostały wykonane, a niektóre były w dziwnym stanie. Jedynym rozwiązaniem było ponowne uruchomienie serwera lub zatrzymanie i uruchomienie usługi Scheduler. Nie jest to coś, co mogę zrobić bez praw administratora i nie jest akceptowane w środowisku produkcyjnym. Tylko moje 0,02 dolara.
SpaceCowboy74
6
Tak naprawdę zdarzyło mi się to na kilku serwerach (do tej pory 3). Nie powiem jednak, że to norma. Po prostu mówię, że czasami nie jest źle wdrożyć własną metodę robienia czegoś.
SpaceCowboy74
7

Albo powinno działać OK. W rzeczywistości System.Threading.Timer używa System.Timers.Timer wewnętrznie.

Powiedziawszy to, łatwo jest nadużywać System.Timers.Timer. Jeśli nie przechowujesz gdzieś obiektu Timer w zmiennej, może on zostać usunięty. Jeśli tak się stanie, twój zegar nie będzie już strzelał. Wywołaj metodę Dispose, aby zatrzymać licznik czasu, lub użyj klasy System.Threading.Timer, która jest nieco ładniejszym opakowaniem.

Jakie problemy widziałeś do tej pory?

Tim Robinson
źródło
Zastanawiam się, dlaczego aplikacja Windows Phone ma dostęp tylko do System.Threading.Timer.
Stonetip
telefony z systemem Windows prawdopodobnie mają lżejszą wersję frameworka, a posiadanie całego tego dodatkowego kodu do korzystania z obu metod prawdopodobnie nie jest potrzebne, więc nie jest uwzględnione. Myślę, że odpowiedź Nicka daje lepszy powód, dla którego telefony z systemem Windows nie mają dostępu, System.Timers.Timerponieważ nie obsługuje zgłoszonych wyjątków.
Malachi,
2

Zgadzam się z poprzednim komentarzem, że najlepiej byłoby rozważyć inne podejście. Sugerowałbym napisanie aplikacji konsolowej i użycie harmonogramu Windows:

To będzie:

  • Zredukuj kod instalacyjny, który replikuje zachowanie harmonogramu
  • Zapewnij większą elastyczność pod względem zachowania harmonogramu (np. Uruchamianie tylko w weekendy) z całą logiką planowania wyekstrahowaną z kodu aplikacji
  • Wykorzystaj argumenty wiersza poleceń dla parametrów bez konieczności ustawiania wartości konfiguracyjnych w plikach konfiguracyjnych itp
  • Znacznie łatwiejsze do debugowania / testowania podczas programowania
  • Zezwalaj użytkownikowi pomocy technicznej na wykonywanie przez bezpośrednie wywołanie aplikacji konsoli (np. Przydatne w sytuacjach pomocy technicznej)
user32378
źródło
3
Ale to wymaga zalogowanego użytkownika? Usługa może być więc lepsza, jeśli będzie działać na serwerze 24 godziny na dobę, 7 dni w tygodniu.
JP Hellemons,
1

Jak już wspomniano, oba System.Threading.Timeri System.Timers.Timerbędą działać. Duża różnica między nimi polega na tym, że drugi jest System.Threading.Timerowijany.

System.Threading.Timerbędzie miał więcej obsługi wyjątków, podczas gdy System.Timers.Timerpołknie wszystkie wyjątki.

W przeszłości sprawiało mi to duże problemy, więc zawsze korzystałem z „System.Threading.Timer” i nadal bardzo dobrze radziłem sobie z wyjątkami.

Nick N.
źródło
0

Wiem, że ten wątek jest trochę stary, ale przydał się w konkretnym scenariuszu, który miałem i pomyślałem, że warto zauważyć, że istnieje inny powód, dla którego System.Threading.Timermoże być dobre podejście. Gdy musisz okresowo wykonywać zadanie, które może zająć dużo czasu i chcesz mieć pewność, że cały okres oczekiwania zostanie wykorzystany między zadaniami lub jeśli nie chcesz, aby zadanie było ponownie uruchamiane przed zakończeniem poprzedniego zadania w przypadku, gdy zadanie trwa dłużej niż okres licznika czasu. Możesz użyć następujących:

using System;
using System.ServiceProcess;
using System.Threading;

    public partial class TimerExampleService : ServiceBase
    {
        private AutoResetEvent AutoEventInstance { get; set; }
        private StatusChecker StatusCheckerInstance { get; set; }
        private Timer StateTimer { get; set; }
        public int TimerInterval { get; set; }

        public CaseIndexingService()
        {
            InitializeComponent();
            TimerInterval = 300000;
        }

        protected override void OnStart(string[] args)
        {
            AutoEventInstance = new AutoResetEvent(false);
            StatusCheckerInstance = new StatusChecker();

            // Create the delegate that invokes methods for the timer.
            TimerCallback timerDelegate =
                new TimerCallback(StatusCheckerInstance.CheckStatus);

            // Create a timer that signals the delegate to invoke 
            // 1.CheckStatus immediately, 
            // 2.Wait until the job is finished,
            // 3.then wait 5 minutes before executing again. 
            // 4.Repeat from point 2.
            Console.WriteLine("{0} Creating timer.\n",
                DateTime.Now.ToString("h:mm:ss.fff"));
            //Start Immediately but don't run again.
            StateTimer = new Timer(timerDelegate, AutoEventInstance, 0, Timeout.Infinite);
            while (StateTimer != null)
            {
                //Wait until the job is done
                AutoEventInstance.WaitOne();
                //Wait for 5 minutes before starting the job again.
                StateTimer.Change(TimerInterval, Timeout.Infinite);
            }
            //If the Job somehow takes longer than 5 minutes to complete then it wont matter because we will always wait another 5 minutes before running again.
        }

        protected override void OnStop()
        {
            StateTimer.Dispose();
        }
    }

    class StatusChecker
        {

            public StatusChecker()
            {
            }

            // This method is called by the timer delegate.
            public void CheckStatus(Object stateInfo)
            {
                AutoResetEvent autoEvent = (AutoResetEvent)stateInfo;
                Console.WriteLine("{0} Start Checking status.",
                    DateTime.Now.ToString("h:mm:ss.fff"));
                //This job takes time to run. For example purposes, I put a delay in here.
                int milliseconds = 5000;
                Thread.Sleep(milliseconds);
                //Job is now done running and the timer can now be reset to wait for the next interval
                Console.WriteLine("{0} Done Checking status.",
                    DateTime.Now.ToString("h:mm:ss.fff"));
                autoEvent.Set();
            }
        }
MigaelM
źródło