Як викликати метод щодня, в певний час, на C #?


83

Я шукав SO і знайшов відповіді про Quartz.net. Але це здається занадто великим для мого проекту. Я хочу рівноцінне рішення, але простіше і (в кращому випадку) в коді (зовнішня бібліотека не потрібна). Як я можу викликати метод щодня, у визначений час?

Мені потрібно додати деяку інформацію про це:

  • найпростіший (і потворний) спосіб це зробити - перевірити час щосекунди / хвилини і викликати метод у потрібний час

Я хочу більш ефективний спосіб зробити це, не потрібно постійно перевіряти час, і я контролюю, чи виконується робота ні. Якщо метод не вдається (через будь-які проблеми), програма повинна знати, як писати журнал / відправляти електронне повідомлення. Тому мені потрібно називати метод, а не планувати роботу.

Я знайшов це рішення Виклик методу у встановлений час у Java на Java. Чи існує подібний спосіб у C #?

EDIT: Я зробив це. Я додав параметр у void Main () і створив bat (запланований Планувальником завдань Windows) для запуску програми з цим параметром. Програма запускається, виконує роботу, а потім виходить. Якщо завдання не вдається, воно може писати журнали та надсилати електронні листи. Такий підхід добре відповідає моїм вимогам :)


2
Зв’язане запитання, схоже, вказує на те, що метод у вашому запущеному додатку потрібно періодично викликати. Так це? Це вплине на те, чи потрібне вам планування в процесі, чи ви можете просто використовувати планувальник Windows.
paxdiablo

моя програма, відповідно до вимог, буде працювати безперервно
Quan Mai

Гей, я не можу повірити, що ти назвав мою відповідь "потворною". Вони борються зі словами :-)
paxdiablo

Не мав на увазі про вашу відповідь: с. Я теж про це думав, і своє мені здалося потворним.
Quan Mai

Пов'язаний з stackoverflow.com/questions/725917
PaulB

Відповіді:


85
  • Створіть консольний додаток, який робить те, що ви шукаєте
  • Використовуйте функціональність Windows « Заплановані завдання », щоб застосувати цю консольну програму в той момент, коли вам потрібно її запустити

Це справді все, що вам потрібно!

Оновлення: якщо ви хочете зробити це всередині своєї програми, у вас є кілька варіантів:

  • у програмі Windows Forms ви можете підключитися до Application.Idleподії та перевірити, чи не досягли ви часу дня для виклику свого методу. Цей метод викликається лише тоді, коли ваш додаток не зайнятий іншими речами. Швидка перевірка, чи досягнуто ваш цільовий час, не повинна сильно напружувати ваш додаток, я думаю ...
  • у веб-програмі ASP.NET існують методи "імітації" розсилання запланованих подій - ознайомтесь із цією статтею CodeProject
  • і звичайно, ви можете просто просто «прокрутити своє» у будь-якому додатку .NET - ознайомтесь із цією статтею CodeProject для зразка реалізації

Оновлення №2 : якщо ви хочете перевіряти кожні 60 хвилин, ви можете створити таймер, який прокидається кожні 60 хвилин, і якщо час закінчується, він викликає метод.

Щось на зразок цього:

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;

а потім у вашому обробнику подій:

void checkForTime_Elapsed(object sender, ElapsedEventArgs e)
{
    if (timeIsReady())
    {
       SendEmail();
    }
}

Думав про це раніше. :). Але моя програма буде працювати безперервно, і я хочу знати альтернативний спосіб, якщо він є :)
Quan Mai

це програма Winform. Я спробую переконати свого боса змінити його дизайн, але спочатку я повинен спробувати виконати його вимоги: p
Quan Mai

4
Що робить timeIsReady()дзвінок?
brimble2010

1
@ brimble2010: він може робити все, що тобі подобається. Ви можете, наприклад, мати таблицю з тимчасово "заблокованими" часовими інтервалами (наприклад , не запускатись в 3, 4 або 5 ранку ) або що завгодно - повністю залежить від вас. Просто додаткова перевірка, крім того, що вона запускається кожні 60 хвилин ....
marc_s

1
Дуже приємне, просте, коротке і чисте рішення, яке чудово працює на сьогоднішній день в Windows 10 &. Дякую.
MinimalTech

19

Я створив простий планувальник, простий у використанні, і вам не потрібно використовувати зовнішню бібліотеку. TaskScheduler - це одна програма, яка зберігає посилання на таймерах, тому таймери не збиратимуть сміття, він може запланувати кілька завдань. Ви можете встановити перший запуск (годину та хвилину), якщо на момент планування цей час закінчився, розпочавши планування на наступний день цього часу. Але налаштувати код легко.

Планування нового завдання настільки просте. Приклад: О 11:52 перше завдання - кожні 15 секунд, другий приклад - кожні 5 секунд. Для щоденного виконання встановіть для параметра 3 значення 24.

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

Моє вікно налагодження:

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

Просто додайте цей клас до свого проекту:

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

new Timer не має методу, який приймає 4 аргументи.
Fandango68,

Я припускаю, що таймер тут від System.Timers? Не могли б ви надати зразок робочої програми? Дякую
Fandango68

1
@ Fandango68 Я використовував таймер із простору імен System.Threading. У System.Timers є ще один таймер. Я думаю, ви використовували помилкове використання вгорі.
jannagy02,

@ jannagy02 Як запланувати завдання на такий день, як понеділок?
Sruthi Varghese

9

Кожного разу, коли я будую програми, які потребують такої функціональності, я завжди використовую Планувальник завдань Windows через просту бібліотеку .NET, яку я знайшов.

Будь ласка, перегляньте мою відповідь на подібне запитання, щоб отримати приклад коду та додаткові пояснення.


8

Як казали інші, ви можете використовувати консольний додаток для запуску за розкладом. Те, що інші не сказали, - це те, що ви можете запустити перехресний процес EventWaitHandle, який ви очікуєте у своєму основному додатку.

Додаток Console:

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

Основна програма:

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

4

Найкращий метод, про який я знаю, і, мабуть, найпростіший - це використання Планувальника завдань Windows для виконання коду в певний час доби або постійне запуск програми та перевірка на певний час доби або написання служби Windows, яка виконує те саме.


4

Я знаю, що це старе, але як щодо цього:

Створіть таймер для спрацьовування під час запуску, який обчислює час до наступного запуску. Під час першого дзвінка часу роботи скасуйте перший таймер і запустіть новий щоденний таймер. змінюйте щодня на погодинний або будь-який, який ви хочете, щоб періодичність була.


9
І спостерігайте, як він не справляється під час переходу на літній час. . .
Jim Mischel

3

Ось спосіб зробити це за допомогою TPL. Не потрібно створювати / розпоряджатися таймером тощо:

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
}

2

Ця маленька програма повинна бути рішенням ;-)

Сподіваюся, це допомагає всім.

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

1

Якщо ви хочете запустити виконуваний файл, використовуйте заплановані завдання Windows. Я збираюся припустити (можливо, помилково), що ви хочете, щоб метод працював у вашій поточній програмі.

Чому б просто не мати потоку, який постійно працює, зберігаючи останню дату виклику методу?

Нехай він прокидається щохвилини (наприклад), і якщо поточний час перевищує вказаний час, а остання збережена дата не є поточною, зателефонуйте методу та оновіть дату.


1

Можливо, це просто я, але здавалося, що більшість з цих відповідей були не повними або не працювали правильно. Я зробив щось дуже швидко та брудне. Сказане не впевнене, наскільки хороша ідея робити це таким чином, але це працює щоразу чудово.

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

1
Наскільки я бачу, це було б напруженим очікуванням і, отже, невдалою ідеєю. en.wikipedia.org/wiki/Busy_waiting
Томмі Іварссон

1

Я нещодавно написав додаток ac #, який доводилося перезапускати щодня. Я усвідомлюю, що це питання давнє, але, думаю, не завадить додати ще одне можливе рішення. Ось як я обробляв щоденні перезапуски у визначений час.

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

Щоб забезпечити збереження таймера, рекомендую створювати його поза методом за допомогою System.Timers.Timer tmrRestart = new System.Timers.Timer()методу. Помістіть метод RestartApp()у подію завантаження форми. Коли програма запускається, вона встановлюватиме значення, AppRestartякщо currentбільше, ніж час перезапуску, ми додаємо 1 день, щоб AppRestartпереконатися, що перезапуск відбувся вчасно, і щоб ми не отримали виняток для введення негативного значення в таймер. У tmrRestart_Elapsedразі запуску будь-якого коду, який вам потрібен, був запущений у цей конкретний час. Якщо ваша програма перезапускається самостійно, вам не обов'язково зупиняти таймер, але це також не зашкодить. Якщо програма не перезапуститься, просто зателефонуйте RestartApp()методу ще раз, і ви будете готові піти.


1

Я знайшов це дуже корисним:

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


0

Замість того, щоб встановлювати час для запуску кожну секунду кожні 60 хвилин, ви можете розрахувати час, що залишився, і встановити таймер на половину (або якусь іншу частку) від цього. Таким чином, ви не стільки перевіряєте час, але й підтримуєте ступінь точності, оскільки інтервал таймера зменшує наближення до наміченого часу.

Наприклад, якщо ви хочете зробити щось через 60 хвилин, інтервали таймерів будуть приблизно такими:

30:00:00, 15:00:00, 07:30:00, 03:45:00, ..., 00:00:01, БІГ!

Я використовую наведений нижче код для автоматичного перезапуску послуги раз на день. Я використовую нитку, оскільки я виявив, що таймери ненадійні протягом тривалих періодів, хоча це дорожче в цьому прикладі, це єдиний, створений для цієї мети, тому це не має значення.

(Перетворено з 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);
    }
}

Примітка. Я встановив мінімальний інтервал 1000 мс, його можна збільшувати, зменшувати або видаляти залежно від потрібної вам точності.

Пам’ятайте, що також слід зупинити потік / таймер, коли ваша програма закриється.


0

Я маю простий підхід до цього. Це створює 1 хвилину затримки до того, як відбувається дія. Ви також можете додати секунди, щоб створити Thread.Sleep (); коротший.

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

0

24 години разів

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";
    }

0

Простий приклад для одного завдання:

using System;
using System.Timers;

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

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

-1

Рішення з 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);
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.