Как вызывать метод ежедневно в определенное время в C #?


83

Я искал на SO и нашел ответы о Quartz.net. Но для моего проекта это кажется слишком большим. Мне нужно эквивалентное решение, но более простое и (в лучшем случае) в коде (внешняя библиотека не требуется). Как я могу вызывать метод ежедневно в определенное время?

Мне нужно добавить информацию об этом:

  • Самый простой (и уродливый) способ сделать это - проверять время каждую секунду / минуту и ​​вызывать метод в нужное время

Мне нужен более эффективный способ сделать это, не нужно постоянно проверять время, и я могу контролировать, выполняется ли работа или нет. Если метод не работает (из-за каких-либо проблем), программа должна знать, что нужно писать в журнал / отправлять электронное письмо. Вот почему мне нужно вызывать метод, а не планировать работу.

Я нашел это решение. Вызов метода в фиксированное время в Java на Java. Есть ли аналогичный способ в C #?

РЕДАКТИРОВАТЬ: Я сделал это. Я добавил параметр в void Main () и создал летучую мышь (запланированную планировщиком задач Windows) для запуска программы с этим параметром. Программа запускается, выполняет задание и завершает работу. Если задание не удается, оно способно вести журнал и отправлять электронную почту. Такой подход хорошо соответствует моим требованиям :)


2
Этот связанный вопрос, кажется, указывает на то, что метод в вашем запущенном приложении должен вызываться периодически. Так ли это? Это повлияет на то, нужно ли вам внутрипроцессное планирование или вы можете просто использовать планировщик Windows.
paxdiablo

моя программа, в соответствии с требованиями, будет работать непрерывно
Quan Mai

Эй, не могу поверить, что ты назвал мой ответ «уродливым». Их борющиеся слова :-)
paxdiablo

Не имел в виду ваш ответ: с. Я тоже подумал об этом, и мой оказался уродливым.
Quan Mai

Ответы:


86
  • Создайте консольное приложение, которое сделает то, что вы ищете
  • Используйте функцию 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 и .Net 4.7.2. !! Спасибо.
MinimalTech

20

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

Планировать новую задачу так просто. Пример: в 11:52 первая задача - каждые 15 секунд, вторая - каждые 5 секунд. Для ежедневного выполнения установите 24 для параметра 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
    });

Мое окно отладки:

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 Как запланировать задачу на определенный день, например, на понедельник?
Срути Варгезе

Это полезно, спасибо.
Джеймс

9

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

Пожалуйста, посмотрите мой ответ на аналогичный вопрос, где вы найдете пример кода и дополнительные объяснения.


8

Как уже говорили другие, вы можете использовать консольное приложение для запуска по расписанию. Другие не сказали, что это приложение может запускать перекрестный процесс EventWaitHandle, который вы ожидаете в своем основном приложении.

Консольное приложение:

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
И наблюдайте, как он терпит неудачу при переходе на летнее время. . .
Джим Мишель

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

Я недавно написал приложение на С #, которое нужно было перезапускать ежедневно. Я понимаю, что это старый вопрос, но не думаю, что добавление другого возможного решения повредит. Вот как я обрабатывал ежедневные перезапуски в указанное время.

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

Чтобы ваш таймер оставался в области видимости, я рекомендую создавать его вне метода using System.Timers.Timer tmrRestart = new System.Timers.Timer()method. Поместите метод RestartApp()в событие загрузки формы. Когда приложение запускается, оно устанавливает значения для AppRestartif 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 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();
        }
    }
}

Это не похоже на таймер, а скорее на интервал, а не на точность.
Джеймс

Время начала таймера устанавливается в переменной scheduleTime. Метод Console.WriteLine («Пора запускать задачу») будет запускаться каждый день в это время.
ikleds

Спасибо за объяснение.
Джеймс

Добро пожаловать!
ikleds 02

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