Jak mogę zaplanować codzienne wykonywanie zadania przez usługę C # systemu Windows?

84

Mam usługę napisaną w C # (.NET 1.1) i chcę, aby co wieczór wykonywała pewne czynności porządkowe o północy. Muszę zachować cały kod zawarty w usłudze, więc jaki jest najłatwiejszy sposób, aby to osiągnąć? Używanie Thread.Sleep()i sprawdzanie przewracania się czasu?

ctrlalt3nd
źródło
10
Naprawdę chcesz stworzyć proces .NET, który pozostaje w pamięci przez cały dzień i robi coś o północy? Dlaczego właściwie nie możesz po zainstalowaniu narzędzia skonfigurować zdarzenia systemowego, które uruchamia się o północy (lub w innym możliwym do skonfigurowania czasie), które uruchamia aplikację, robi swoje, a następnie znika?
Robert P
6
Wiem, że byłbym trochę wkurzony, gdybym się dowiedział, że mam zarządzaną usługę, która zużywa 20-40 megabajtów pamięci, działa przez cały czas, a która nie robi nic oprócz raz dziennie. :)
Robert P
jego zduplikowany link
csa

Odpowiedzi:

86

Nie użyłbym Thread.Sleep (). Użyj zaplanowanego zadania (jak wspominali inni) lub ustaw licznik czasu w swojej usłudze, który jest uruchamiany okresowo (na przykład co 10 minut) i sprawdź, czy data zmieniła się od ostatniego uruchomienia:

private Timer _timer;
private DateTime _lastRun = DateTime.Now.AddDays(-1);

protected override void OnStart(string[] args)
{
    _timer = new Timer(10 * 60 * 1000); // every 10 minutes
    _timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
    _timer.Start();
    //...
}


private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    // ignore the time, just compare the date
    if (_lastRun.Date < DateTime.Now.Date)
    {
        // stop the timer while we are running the cleanup task
        _timer.Stop();
        //
        // do cleanup stuff
        //
        _lastRun = DateTime.Now;
        _timer.Start();
    }
}
M4N
źródło
28
Czy nie byłoby sensowniej ustawić licznik czasu na wygaśnięcie o północy, zamiast budzić się co minutę, aby sprawdzić godzinę?
Kibbee
11
@Kibbee: może jeśli usługa nie działa o północy (z jakiegokolwiek powodu), możesz chcieć wykonać zadanie, gdy tylko usługa zostanie ponownie uruchomiona.
M4N
4
Następnie należy dodać kod podczas uruchamiania usługi, aby dowiedzieć się, czy należy od razu uruchomić, ponieważ usługa nie była uruchomiona, a powinno być. Nie powinieneś sprawdzać w sposób ciągły co 10 minut. Sprawdź, czy powinieneś uruchomić się przy starcie, a jeśli nie, dowiedz się, kiedy następnym razem uruchomić. potem i ustaw zegar na tak długo, jak potrzebujesz.
Kibbee
3
@Kibbee: tak, zgadzam się (to drobny szczegół, o którym rozmawiamy). Ale nawet jeśli timer odpala co 10 sekund, nie wyrządziłby żadnej szkody (tj. Nie jest to czasochłonne).
M4N
1
dobre rozwiązanie, ale w powyższym kodzie brakuje _timer.start () i _lastRun należy ustawić na wczoraj w następujący sposób: private DateTime _lastRun = DateTime.Now.AddDays (-1);
Zulu Z
72

Sprawdź Quartz.NET . Możesz go używać w ramach usługi Windows. Umożliwia uruchamianie zadań w oparciu o skonfigurowany harmonogram, a nawet obsługuje prostą składnię „zadania cron”. Odniosłem z tym wiele sukcesów.

Oto krótki przykład jego użycia:

// Instantiate the Quartz.NET scheduler
var schedulerFactory = new StdSchedulerFactory();
var scheduler = schedulerFactory.GetScheduler();

// Instantiate the JobDetail object passing in the type of your
// custom job class. Your class merely needs to implement a simple
// interface with a single method called "Execute".
var job = new JobDetail("job1", "group1", typeof(MyJobClass));

// Instantiate a trigger using the basic cron syntax.
// This tells it to run at 1AM every Monday - Friday.
var trigger = new CronTrigger(
    "trigger1", "group1", "job1", "group1", "0 0 1 ? * MON-FRI");

// Add the job to the scheduler
scheduler.AddJob(job, true);
scheduler.ScheduleJob(trigger);
jeremcc
źródło
2
Dobra sugestia - jak tylko zrobisz coś nawet nieco bardziej złożonego niż podstawowe rzeczy związane z Timerem, powinieneś spojrzeć na Quartz.
serg10
2
Kwarc jest tak łatwy w użyciu, że twierdziłbym, że nawet w przypadku bardzo prostego zadania po prostu użyj go. Umożliwi to łatwe dostosowanie harmonogramu.
Andy White
Czy naprawdę nie jest super proste? Większość nowicjuszy złapie ten problem tutaj stackoverflow.com/questions/24628372/…
Emil
21

Codzienne zadanie? Wygląda na to, że powinno to być zaplanowane zadanie (panel sterowania) - tutaj nie ma potrzeby korzystania z usługi.

Marc Gravell
źródło
15

Czy to musi być rzeczywista usługa? Czy możesz po prostu użyć wbudowanych zaplanowanych zadań w panelu sterowania systemu Windows.

Ian Jacobs
źródło
Ponieważ usługa już istnieje, może być łatwiej dodać tam tę funkcję.
M4N
1
Konwersja usługi o wąskim przeznaczeniu, takiej jak ta, na aplikację konsolową powinna być banalna.
Stephen Martin
7
Powiedział już: „Muszę zachować cały kod zawarty w usłudze”. Nie uważam, że konieczne jest kwestionowanie pierwotnych wymagań. To zależy, ale czasami istnieją bardzo dobre powody, aby korzystać z usługi w ramach zaplanowanego zadania.
jeremcc
3
+1: Ponieważ zawsze musisz patrzeć na swoje problemy ze wszystkich stron.
GvS
6
Gdybym naprawdę miał tylko jedno zadanie do wykonania dziennie, myślę, że prosta aplikacja konsolowa uruchamiana z zaplanowanym zadaniem byłaby w porządku. Chodzi mi o to, że wszystko zależy od sytuacji, a powiedzenie „Nie używaj usługi systemu Windows” nie jest użyteczną odpowiedzią IMO.
jeremcc
3

Sposób, w jaki to robię, to zegar.

Uruchom licznik czasu serwera, niech sprawdza godzinę / minutę co 60 sekund.

Jeśli jest to właściwa godzina / minuta, uruchom proces.

Właściwie to wyekstrahowałem to do klasy bazowej, którą nazywam OnceADayRunner.

Pozwól mi trochę wyczyścić kod i opublikuję go tutaj.

    private void OnceADayRunnerTimer_Elapsed(object sender, ElapsedEventArgs e)
    {
        using (NDC.Push(GetType().Name))
        {
            try
            {
                log.DebugFormat("Checking if it's time to process at: {0}", e.SignalTime);
                log.DebugFormat("IsTestMode: {0}", IsTestMode);

                if ((e.SignalTime.Minute == MinuteToCheck && e.SignalTime.Hour == HourToCheck) || IsTestMode)
                {
                    log.InfoFormat("Processing at: Hour = {0} - Minute = {1}", e.SignalTime.Hour, e.SignalTime.Minute);
                    OnceADayTimer.Enabled = false;
                    OnceADayMethod();
                    OnceADayTimer.Enabled = true;

                    IsTestMode = false;
                }
                else
                {
                    log.DebugFormat("Not correct time at: Hour = {0} - Minute = {1}", e.SignalTime.Hour, e.SignalTime.Minute);
                }
            }
            catch (Exception ex)
            {
                OnceADayTimer.Enabled = true;
                log.Error(ex.ToString());
            }

            OnceADayTimer.Start();
        }
    }

Wołowina metody znajduje się w kontroli e.SignalTime.Minute / Hour.

Są tam haczyki do testowania itp., Ale tak mógłby wyglądać Twój upływający czas, aby wszystko działało.

CubanX
źródło
Niewielkie opóźnienie spowodowane zajętym serwerem może spowodować utratę godziny i minuty.
Jon B
1
Prawdziwe. Kiedy to zrobiłem, sprawdziłem: Czy czas jest teraz większy niż 00:00? Jeśli tak, czy minęło więcej niż 24 godziny od ostatniego wykonywania pracy? Jeśli tak, uruchom zadanie, w przeciwnym razie zaczekaj ponownie.
ZombieSheep
2

Jak już pisali inni, zegar jest najlepszą opcją w opisanym przez ciebie scenariuszu.

W zależności od Twoich wymagań, sprawdzanie aktualnego czasu co minutę może nie być konieczne. Jeśli nie musisz wykonywać akcji dokładnie o północy, ale w ciągu jednej godziny po północy, możesz skorzystać z podejścia Martina i sprawdzić, czy zmieniła się data.

Jeśli powodem, dla którego chcesz wykonać swoją akcję o północy, jest to, że spodziewasz się niskiego obciążenia komputera, lepiej zachowaj ostrożność: to samo założenie jest często przyjmowane przez innych i nagle masz 100 czynności porządkujących rozpoczynających się między 0:00 a 0 : 01 am

W takim przypadku powinieneś rozważyć rozpoczęcie czyszczenia w innym czasie. Zwykle robię te rzeczy nie o godzinie zegarowej, ale o pół godziny (1.30 to moje osobiste preferencje)

Treb
źródło
1

Sugerowałbym użycie timera, ale ustaw go tak, aby sprawdzał co 45 sekund, a nie minutę. W przeciwnym razie możesz spotkać się z sytuacjami, w których przy dużym obciążeniu sprawdzenie dla określonej minuty zostanie pominięte, ponieważ między czasem wyzwolenia timera a czasem uruchomienia kodu i sprawdzenia aktualnego czasu możesz przegapić docelową minutę.

GWLlosa
źródło
Jeśli nie sprawdzasz, aby upewnić się, że nie uruchomił się już raz, istnieje możliwość, że możesz dwukrotnie trafić 00:00, jeśli sprawdzasz kiedykolwiek 45 sekund.
Michael Meadows
Słuszna uwaga. Tego problemu można uniknąć, wywołując Sleep (15000) pod koniec procedury sprawdzania. (a może 16000 ms, na wszelki
wypadek
Zgadzam się, powinieneś sprawdzić, czy jest już uruchomiony, niezależnie od wybranego czasu timera.
GWLlosa
0

Dla tych, dla których powyższe rozwiązania nie działają, dzieje się tak dlatego, że możesz mieć thiswewnątrz swojej klasy, co implikuje metodę rozszerzającą, która, jak mówi komunikat o błędzie, ma sens tylko w nieogólnej klasie statycznej. Twoja klasa nie jest statyczna. Wydaje się, że nie ma to sensu jako metoda rozszerzenia, ponieważ działa w danej instancji, więc usuń rozszerzenie this.

Fandango68
źródło
-2

Spróbuj tego:

public partial class Service : ServiceBase
{
    private Timer timer;
    public Service()
    {
        InitializeComponent();
    }

    protected override void OnStart(string[] args)
    {
        SetTimer();
    }

    private void SetTimer()
    {
        if (timer == null)
        {
            timer = new Timer();
            timer.AutoReset = true;
            timer.Interval = 60000 * Convert.ToDouble(ConfigurationManager.AppSettings["IntervalMinutes"]);
            timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
            timer.Start();
        }
    }

    private void timer_Elapsed(object source, System.Timers.ElapsedEventArgs e)
    {
        //Do some thing logic here
    }

    protected override void OnStop()
    {
        // disposed all service objects
    }
}
Shailendra Tiwari
źródło