Jak wywołać metodę codziennie, o określonej godzinie, w C #?

83

Szukałem w SO i znalazłem odpowiedzi na temat Quartz.net. Ale wydaje się, że jest za duży dla mojego projektu. Chcę równoważnego rozwiązania, ale prostszego i (w najlepszym przypadku) w kodzie (nie jest wymagana żadna zewnętrzna biblioteka). Jak mogę wywoływać metodę codziennie, o określonej godzinie?

Muszę dodać kilka informacji na ten temat:

  • najprostszym (i brzydkim) sposobem na to jest sprawdzanie czasu co sekundę / minutę i wywoływanie metody we właściwym czasie

Chcę skuteczniejszego sposobu, aby to zrobić, bez konieczności ciągłego sprawdzania czasu i mam kontrolę nad tym, czy praca jest wykonana, czy nie. Jeśli metoda nie powiedzie się (z powodu jakichkolwiek problemów), program powinien wiedzieć, aby zapisać do logowania / wysłać wiadomość e-mail. Dlatego muszę wywołać metodę, a nie zaplanować pracę.

Znalazłem to rozwiązanie. Wywołaj metodę w ustalonym czasie w Javie w Javie. Czy jest podobny sposób w C #?

EDYCJA: Zrobiłem to. Dodałem parametr do void Main () i utworzyłem bat (zaplanowany przez Harmonogram zadań systemu Windows), aby uruchomić program z tym parametrem. Program działa, wykonuje swoją pracę, a następnie kończy pracę. Jeśli zadanie się nie powiedzie, jest w stanie zapisać dziennik i wysłać e-mail. Takie podejście dobrze pasuje do moich wymagań :)

Quan Mai
źródło
2
To powiązane pytanie wydaje się wskazywać, że metoda w uruchomionej aplikacji musi być wywoływana okresowo. Czy tak jest? Będzie to miało wpływ na to, czy potrzebujesz planowania w procesie, czy możesz po prostu użyć harmonogramu systemu Windows.
paxdiablo
mój program będzie, zgodnie z wymaganiami, działał w sposób ciągły
Quan Mai
Hej, nie mogę uwierzyć, że nazwałeś moją odpowiedź „brzydką”. Ich walczące słowa :-)
paxdiablo
Nie miałem na myśli twojej odpowiedzi: str. Też o tym myślałem i uznałem, że mój jest brzydki.
Quan Mai

Odpowiedzi:

86
  • Utwórz aplikację konsolową, która robi to, czego szukasz
  • Użyj funkcji „ Zaplanowane zadania ” systemu Windows, aby ta aplikacja konsoli była wykonywana w momencie, gdy jest to potrzebne do uruchomienia

To naprawdę wszystko, czego potrzebujesz!

Aktualizacja: jeśli chcesz to zrobić w swojej aplikacji, masz kilka opcji:

  • w aplikacji Windows Forms możesz dotknąć Application.Idlezdarzenia i sprawdzić, czy osiągnąłeś odpowiednią porę dnia na wywołanie metody. Ta metoda jest wywoływana tylko wtedy, gdy Twoja aplikacja nie jest zajęta innymi rzeczami. Szybkie sprawdzenie, czy osiągnięto docelowy czas, nie powinno zbytnio obciążać Twojej aplikacji, myślę, że ...
  • w aplikacji internetowej ASP.NET istnieją metody „symulowania” wysyłania zaplanowanych zdarzeń - zapoznaj się z tym artykułem w CodeProject
  • i oczywiście możesz też po prostu „rzucić własną” w dowolnej aplikacji .NET - zapoznaj się z tym artykułem w CodeProject, aby zapoznać się z przykładową implementacją

Aktualizacja nr 2 : jeśli chcesz sprawdzać co 60 minut, możesz utworzyć licznik czasu, który budzi się co 60 minut, a jeśli czas się skończy, wywołuje metodę.

Coś takiego:

using System.Timers;

const double interval60Minutes = 60 * 60 * 1000; // milliseconds to one hour

Timer checkForTime = new Timer(interval60Minutes);
checkForTime.Elapsed += new ElapsedEventHandler(checkForTime_Elapsed);
checkForTime.Enabled = true;

a następnie w programie obsługi zdarzeń:

void checkForTime_Elapsed(object sender, ElapsedEventArgs e)
{
    if (timeIsReady())
    {
       SendEmail();
    }
}
marc_s
źródło
Myślałem o tym wcześniej. :). Ale mój program będzie działał w sposób ciągły i chcę poznać alternatywny sposób, jeśli jest :)
Quan Mai
jest to aplikacja Winform. Spróbuję przekonać szefa do zmiany projektu, ale najpierw powinienem spróbować spełnić jego wymagania: p
Quan Mai
4
Co robi ta timeIsReady()rozmowa?
brimble2010
1
@ brimble2010: może robić wszystko, co chcesz. Możesz na przykład mieć stół z tymczasowo „zablokowanymi” przedziałami czasowymi (np. Nie graj o 3, 4 lub 5 rano ) lub cokolwiek - całkowicie zależy od Ciebie. Tylko dodatkowe sprawdzenie, oprócz uruchamiania co 60 minut ...
marc_s
1
Bardzo ładne, proste, krótkie i przejrzyste rozwiązanie, które do dziś działa doskonale w Windows 10 i .Net 4.7.2. !! Dziękuję Ci.
MinimalTech
20

Stworzyłem prosty harmonogram, który jest łatwy w użyciu i nie wymaga korzystania z zewnętrznej biblioteki. TaskScheduler to singleton, który przechowuje odwołania do timerów, dzięki czemu timery nie będą zbierane jako śmieci, ale może zaplanować wiele zadań. Można ustawić pierwsze uruchomienie (godzinę i minutę), jeśli w momencie planowania ten czas się skończył, planowanie rozpocznie się następnego dnia o tej godzinie. Ale łatwo jest dostosować kod.

Planowanie nowego zadania jest takie proste. Przykład: O 11:52 pierwsze zadanie jest wykonywane co 15 sekund, a drugi przykład co 5 sekund. Do codziennego wykonywania należy ustawić parametr 24 na 3.

TaskScheduler.Instance.ScheduleTask(11, 52, 0.00417, 
    () => 
    {
        Debug.WriteLine("task1: " + DateTime.Now);
        //here write the code that you want to schedule
    });

TaskScheduler.Instance.ScheduleTask(11, 52, 0.00139,
    () =>
    {
        Debug.WriteLine("task2: " + DateTime.Now);
        //here write the code that you want to schedule
    });

Moje okno debugowania:

task2: 07.06.2017 11:52:00
task1: 07.06.2017 11:52:00
task2: 07.06.2017 11:52:05
task2: 07.06.2017 11:52:10
task1: 07.06.2017 11:52:15
task2: 07.06.2017 11:52:15
task2: 07.06.2017 11:52:20
task2: 07.06.2017 11:52:25
...

Po prostu dodaj tę klasę do swojego projektu:

public class TaskScheduler
{
    private static TaskScheduler _instance;
    private List<Timer> timers = new List<Timer>();

    private TaskScheduler() { }

    public static TaskScheduler Instance => _instance ?? (_instance = new TaskScheduler());

    public void ScheduleTask(int hour, int min, double intervalInHour, Action task)
    {
        DateTime now = DateTime.Now;
        DateTime firstRun = new DateTime(now.Year, now.Month, now.Day, hour, min, 0, 0);
        if (now > firstRun)
        {
            firstRun = firstRun.AddDays(1);
        }

        TimeSpan timeToGo = firstRun - now;
        if (timeToGo <= TimeSpan.Zero)
        {
            timeToGo = TimeSpan.Zero;
        }

        var timer = new Timer(x =>
        {
            task.Invoke();
        }, null, timeToGo, TimeSpan.FromHours(intervalInHour));

        timers.Add(timer);
    }
}
jannagy02
źródło
new Timer nie ma metody, która przyjmuje 4 argumenty.
Fandango68
Zakładam, że Timer jest tutaj z System.Timers? Czy mógłbyś podać przykładowy program roboczy? Dzięki
Fandango68
1
@ Fandango68 Użyłem Timera z przestrzeni nazw System.Threading. W System.Timers jest inny Timer. Myślę, że użyłeś fałszywego użycia na górze.
jannagy02
@ jannagy02 Jak zaplanować zadanie na określony dzień, np. poniedziałek?
Sruthi Varghese
To przydatne, dzięki.
James
9

Zawsze, gdy tworzę aplikacje, które wymagają takiej funkcjonalności, zawsze korzystam z Harmonogramu zadań systemu Windows za pośrednictwem prostej biblioteki .NET , którą znalazłem.

Proszę zobaczyć moją odpowiedź na podobne pytanie dla niektórych przykładowy kod i więcej wyjaśnień.

Maxim Zaslavsky
źródło
8

Jak powiedzieli inni, możesz użyć aplikacji konsoli do uruchamiania zgodnie z harmonogramem. Inni nie powiedzieli, że ta aplikacja może wywołać proces EventWaitHandle, na który czekasz w swojej głównej aplikacji.

Aplikacja konsolowa:

class Program
{
    static void Main(string[] args)
    {
        EventWaitHandle handle = 
            new EventWaitHandle(true, EventResetMode.ManualReset, "GoodMutexName");
        handle.Set();
    }
}

Główna aplikacja:

private void Form1_Load(object sender, EventArgs e)
{
    // Background thread, will die with application
    ThreadPool.QueueUserWorkItem((dumby) => EmailWait());
}

private void EmailWait()
{
    EventWaitHandle handle = 
        new EventWaitHandle(false, EventResetMode.ManualReset, "GoodMutexName");

    while (true)
    {
        handle.WaitOne();

        SendEmail();

        handle.Reset();
    }
}

źródło
4

Najlepszą metodą, jaką znam i prawdopodobnie najprostszą, jest użycie Harmonogramu zadań systemu Windows do wykonania kodu o określonej porze dnia lub uruchomienie aplikacji na stałe i sprawdzenie określonej porze dnia lub napisanie usługi systemu Windows, która wykonuje podobnie.

Pieter Germishuys
źródło
4

Wiem, że to jest stare, ale co powiesz na to:

Zbuduj licznik czasu, który będzie uruchamiany podczas uruchamiania, który oblicza czas do następnego uruchomienia. Przy pierwszym wywołaniu środowiska wykonawczego anuluj pierwszy zegar i uruchom nowy dzienny licznik czasu. zmieniać codziennie na co godzinę lub cokolwiek chcesz, aby okresowość była.

mansoor
źródło
9
I patrz, jak zawodzi podczas zmiany czasu letniego. . .
Jim Mischel,
3

Oto sposób na zrobienie tego za pomocą TPL. Nie ma potrzeby tworzenia / pozbywania się timera itp .:

void ScheduleSomething()
{

    var runAt = DateTime.Today + TimeSpan.FromHours(16);

    if (runAt <= DateTime.Now)
    {
        DoSomething();
    }
    else
    {
        var delay = runAt - DateTime.Now;
        System.Threading.Tasks.Task.Delay(delay).ContinueWith(_ => DoSomething());
    }

}

void DoSomething()
{
    // do somethig
}
Gino
źródło
2

Ten mały program powinien być rozwiązaniem ;-)

Mam nadzieję, że to pomoże wszystkim.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace DailyWorker
{
    class Program
    {
        static void Main(string[] args)
        {
            var cancellationSource = new CancellationTokenSource();

            var utils = new Utils();
            var task = Task.Run(
                () => utils.DailyWorker(12, 30, 00, () => DoWork(cancellationSource.Token), cancellationSource.Token));

            Console.WriteLine("Hit [return] to close!");
            Console.ReadLine();

            cancellationSource.Cancel();
            task.Wait();
        }

        private static void DoWork(CancellationToken token)
        {
            while (!token.IsCancellationRequested)
            {
                Console.Write(DateTime.Now.ToString("G"));
                Console.CursorLeft = 0;
                Task.Delay(1000).Wait();
            }
        }
    }

    public class Utils
    {
        public void DailyWorker(int hour, int min, int sec, Action someWork, CancellationToken token)
        {
            while (!token.IsCancellationRequested)
            {
                var dateTimeNow = DateTime.Now;
                var scanDateTime = new DateTime(
                    dateTimeNow.Year,
                    dateTimeNow.Month,
                    dateTimeNow.Day,
                    hour,       // <-- Hour when the method should be started.
                    min,  // <-- Minutes when the method should be started.
                    sec); // <-- Seconds when the method should be started.

                TimeSpan ts;
                if (scanDateTime > dateTimeNow)
                {
                    ts = scanDateTime - dateTimeNow;
                }
                else
                {
                    scanDateTime = scanDateTime.AddDays(1);
                    ts           = scanDateTime - dateTimeNow;
                }

                try
                {
                     Task.Delay(ts).Wait(token);
                }
                catch (OperationCanceledException)
                {
                    break;
                }

                // Method to start
                someWork();
            }
        }
    }
}
Marcus
źródło
1

Jeśli chcesz, aby plik wykonywalny działał, użyj Zaplanowanych zadań systemu Windows. Założę (być może błędnie), że chcesz, aby metoda działała w Twoim bieżącym programie.

Dlaczego po prostu nie uruchomić wątku w sposób ciągły przechowujący ostatnią datę wywołania metody?

Niech budzi się co minutę (na przykład) i, jeśli bieżący czas jest dłuższy niż określony czas, a ostatnia zapisana data nie jest aktualną datą, wywołaj metodę, a następnie zaktualizuj datę.

paxdiablo
źródło
1

Może to tylko ja, ale wydawało się, że większość z tych odpowiedzi nie była kompletna lub nie będzie działać poprawnie. Zrobiłem coś bardzo szybko i brudno. Biorąc to pod uwagę, nie jestem pewien, jak dobry jest pomysł zrobienia tego w ten sposób, ale za każdym razem działa idealnie.

while (true)
{
    if(DateTime.Now.ToString("HH:mm") == "22:00")
    {
        //do something here
        //ExecuteFunctionTask();
        //Make sure it doesn't execute twice by pausing 61 seconds. So that the time is past 2200 to 2201
        Thread.Sleep(61000);
    }

    Thread.Sleep(10000);
}
Deathstalker
źródło
1
O ile widzę, byłoby to zajęte oczekiwanie i dlatego nie jest to dobry pomysł. en.wikipedia.org/wiki/Busy_waiting
Tommy Ivarsson
1

Niedawno napisałem aplikację ac #, która musiała być codziennie restartowana. Zdaję sobie sprawę, że to pytanie jest stare, ale nie sądzę, by boli dodanie kolejnego możliwego rozwiązania. W ten sposób radziłem sobie z codziennymi restartami o określonej godzinie.

public void RestartApp()
{
  AppRestart = AppRestart.AddHours(5);
  AppRestart = AppRestart.AddMinutes(30);
  DateTime current = DateTime.Now;
  if (current > AppRestart) { AppRestart = AppRestart.AddDays(1); }

  TimeSpan UntilRestart = AppRestart - current;
  int MSUntilRestart = Convert.ToInt32(UntilRestart.TotalMilliseconds);

  tmrRestart.Interval = MSUntilRestart;
  tmrRestart.Elapsed += tmrRestart_Elapsed;
  tmrRestart.Start();
}

Aby mieć pewność, że licznik czasu jest utrzymywany w zakresie, zalecam utworzenie go poza metodą przy użyciu System.Timers.Timer tmrRestart = new System.Timers.Timer()metody. Umieść metodę RestartApp()w zdarzeniu ładowania formularza. Gdy aplikacja zostanie uruchomiona, ustawi wartości, AppRestartjeśli currentjest większe niż czas ponownego uruchomienia, dodamy 1 dzień, AppRestartaby upewnić się, że ponowne uruchomienie nastąpi na czas i że nie otrzymamy wyjątku od umieszczenia wartości ujemnej w liczniku czasu. W tmrRestart_Elapsedprzypadku uruchomienia dowolnego kodu, którego potrzebujesz, uruchomiono w tym określonym czasie. Jeśli Twoja aplikacja uruchomi się ponownie samoczynnie, niekoniecznie musisz zatrzymywać licznik czasu, ale to też nie zaszkodzi.Jeśli aplikacja nie uruchomi się ponownie, po prostu wywołaj RestartApp()metodę ponownie, a będziesz gotowy do pracy.

Fancy_Mammoth
źródło
1

Uważam, że to bardzo przydatne:

using System;
using System.Timers;

namespace ScheduleTimer
{
    class Program
    {
        static Timer timer;

        static void Main(string[] args)
        {
            schedule_Timer();
            Console.ReadLine();
        }

        static void schedule_Timer()
        {
            Console.WriteLine("### Timer Started ###");

            DateTime nowTime = DateTime.Now;
            DateTime scheduledTime = new DateTime(nowTime.Year, nowTime.Month, nowTime.Day, 8, 42, 0, 0); //Specify your scheduled time HH,MM,SS [8am and 42 minutes]
            if (nowTime > scheduledTime)
            {
                scheduledTime = scheduledTime.AddDays(1);
            }

            double tickTime = (double)(scheduledTime - DateTime.Now).TotalMilliseconds;
            timer = new Timer(tickTime);
            timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
            timer.Start();
        }

        static void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            Console.WriteLine("### Timer Stopped ### \n");
            timer.Stop();
            Console.WriteLine("### Scheduled Task Started ### \n\n");
            Console.WriteLine("Hello World!!! - Performing scheduled task\n");
            Console.WriteLine("### Task Finished ### \n\n");
            schedule_Timer();
        }
    }
}
JoKeRxbLaCk
źródło
0

Zamiast ustawiać czas uruchamiania co sekundę co 60 minut, możesz obliczyć pozostały czas i ustawić licznik na połowę (lub inną część) tego. W ten sposób nie sprawdzasz czasu tak często, ale także zachowujesz pewien stopień dokładności, ponieważ interwał timera zmniejsza się, im bliżej jesteś czasu docelowego.

Na przykład, jeśli chciałbyś zrobić coś za 60 minut od teraz, interwały timerów byłyby w przybliżeniu:

30:00:00, 15:00:00, 07:30:00, 03:45:00, ..., 00:00:01, RUN!

Używam poniższego kodu, aby automatycznie ponownie uruchamiać usługę raz dziennie. Używam wątku, ponieważ stwierdziłem, że liczniki czasu są zawodne przez długi czas, podczas gdy w tym przykładzie jest to bardziej kosztowne, jest to jedyny utworzony w tym celu, więc nie ma to znaczenia.

(Przekonwertowane z VB.NET)

autoRestartThread = new System.Threading.Thread(autoRestartThreadRun);
autoRestartThread.Start();

...

private void autoRestartThreadRun()
{
    try {
        DateTime nextRestart = DateAndTime.Today.Add(CurrentSettings.AutoRestartTime);
        if (nextRestart < DateAndTime.Now) {
            nextRestart = nextRestart.AddDays(1);
        }

        while (true) {
            if (nextRestart < DateAndTime.Now) {
                LogInfo("Auto Restarting Service");
                Process p = new Process();
                p.StartInfo.FileName = "cmd.exe";
                p.StartInfo.Arguments = string.Format("/C net stop {0} && net start {0}", "\"My Service Name\"");
                p.StartInfo.LoadUserProfile = false;
                p.StartInfo.UseShellExecute = false;
                p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
                p.StartInfo.CreateNoWindow = true;
                p.Start();
            } else {
                dynamic sleepMs = Convert.ToInt32(Math.Max(1000, nextRestart.Subtract(DateAndTime.Now).TotalMilliseconds / 2));
                System.Threading.Thread.Sleep(sleepMs);
            }
        }
    } catch (ThreadAbortException taex) {
    } catch (Exception ex) {
        LogError(ex);
    }
}

Uwaga Ustawiłem minimalny interwał 1000 ms, który można zwiększyć, zmniejszyć lub usunąć w zależności od wymaganej dokładności.

Pamiętaj, aby zatrzymać wątek / licznik czasu po zamknięciu aplikacji.

apc
źródło
0

Mam do tego proste podejście. Powoduje to 1-minutowe opóźnienie przed rozpoczęciem działania. Możesz również dodać sekundy, aby Thread.Sleep (); krótszy.

private void DoSomething(int aHour, int aMinute)
{
    bool running = true;
    while (running)
    {
        Thread.Sleep(1);
        if (DateTime.Now.Hour == aHour && DateTime.Now.Minute == aMinute)
        {
            Thread.Sleep(60 * 1000); //Wait a minute to make the if-statement false
            //Do Stuff
        }
    }
}
Gustav Bok
źródło
0

24 godziny razy

var DailyTime = "16:59:00";
            var timeParts = DailyTime.Split(new char[1] { ':' });

            var dateNow = DateTime.Now;
            var date = new DateTime(dateNow.Year, dateNow.Month, dateNow.Day,
                       int.Parse(timeParts[0]), int.Parse(timeParts[1]), int.Parse(timeParts[2]));
            TimeSpan ts;
            if (date > dateNow)
                ts = date - dateNow;
            else
            {
                date = date.AddDays(1);
                ts = date - dateNow;
            }

            //waits certan time and run the code
            Task.Delay(ts).ContinueWith((x) => OnTimer());

public void OnTimer()
    {
        ViewBag.ErrorMessage = "EROOROOROROOROR";
    }
Pandurang Chopade
źródło
0

Prosty przykład jednego zadania:

using System;
using System.Timers;

namespace ConsoleApp
{
    internal class Scheduler
    {
        private static readonly DateTime scheduledTime = 
            new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 10, 0, 0);
        private static DateTime dateTimeLastRunTask;

        internal static void CheckScheduledTask()
        {
            if (dateTimeLastRunTask.Date < DateTime.Today && scheduledTime.TimeOfDay < DateTime.Now.TimeOfDay)
            {
                Console.WriteLine("Time to run task");
                dateTimeLastRunTask = DateTime.Now;
            }
            else
            {
                Console.WriteLine("not yet time");
            }
        }
    }

    internal class Program
    {
        private static Timer timer;

        static void Main(string[] args)
        {
            timer = new Timer(5000);
            timer.Elapsed += OnTimer;
            timer.Start();
            Console.ReadLine();
        }

        private static void OnTimer(object source, ElapsedEventArgs e)
        {
            Scheduler.CheckScheduledTask();
        }
    }
}
ikleds
źródło
To nie wygląda jak zegar, zamiast tego wygląda jak interwał, nie jest dokładny.
james
Czas rozpoczęcia timera jest ustawiony w zmiennej ScheduleTime. Metoda Console.WriteLine („Time to run task”) będzie uruchamiana codziennie o tej godzinie.
ikleds
Dziękuję za wyjaśnienie.
James
Zapraszamy!
ikleds
-1

Rozwiązanie z System.Threading.Timer:

    private void nameOfMethod()
    {
        //do something
    }

    /// <summary>
    /// run method at 22:00 every day
    /// </summary>
    private void runMethodEveryDay()
    {
        var runAt = DateTime.Today + TimeSpan.FromHours(22);

        if(runAt.Hour>=22)
            runAt = runAt.AddDays(1.00d); //if aplication is started after 22:00 

        var dueTime = runAt - DateTime.Now; //time before first run ; 

        long broj3 = (long)dueTime.TotalMilliseconds;
        TimeSpan ts2 = new TimeSpan(24, 0, 1);//period of repeating method
        long broj4 = (long)ts2.TotalMilliseconds;
        timer2 = new System.Threading.Timer(_ => nameOfMethod(), null, broj3, broj4);
    }
zvonimir
źródło