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?


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

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

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.


15

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


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.


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)


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ę.


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.


-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
    }
}
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.