How to call a method daily, at specific time, in C#?
Asked Answered
G

19

100

I've searched on SO and found answers about Quartz.net. But it seems to be too big for my project. I want an equivalent solution, but simpler and (at best) in-code (no external library required). How can I call a method daily, at a specific time?

I need to add some information about this:

  • the simplest (and ugly) way to do this, is check the time every second/minute and call the method, at right time

I want a more-effective way to do this, no need to check the time constantly, and I have control about whether the job is done a not. If the method fails (because of any problems), the program should know to write to log/send a email. That's why I need to call a method, not schedule a job.

I found this solution Call a method at fixed time in Java in Java. Is there a similar way in C#?

EDIT: I've done this. I added a parameter into void Main(), and created a bat (scheduled by Windows Task Scheduler) to run the program with this parameter. The program runs, does the job, and then exits. If a job fails, it's capable of writing log and sending email. This approach fits my requirements well :)

Grappling answered 14/7, 2010 at 4:14 Comment(5)
That linked question seems to indicate that a method within your running app has to be called periodically. Is that the case? It will impact whether you need in-process scheduling or whether you can just use the Windows scheduler.Sticky
my program will, as requirements, run continuouslyGrappling
Hey, I can't believe you called my answer "ugly". Them's fightin' words :-)Sticky
Didn't mean about your answer :p. I thought about it, too, and I found mine ugly.Grappling
Linked to stackoverflow.com/questions/725917Surbased
E
94
  • Create a console app that does what you're looking for
  • Use the Windows "Scheduled Tasks" functionality to have that console app executed at the time you need it to run

That's really all you need!

Update: if you want to do this inside your app, you have several options:

  • in a Windows Forms app, you could tap into the Application.Idle event and check to see whether you've reached the time in the day to call your method. This method is only called when your app isn't busy with other stuff. A quick check to see if your target time has been reached shouldn't put too much stress on your app, I think...
  • in a ASP.NET web app, there are methods to "simulate" sending out scheduled events - check out this CodeProject article
  • and of course, you can also just simply "roll your own" in any .NET app - check out this CodeProject article for a sample implementation

Update #2: if you want to check every 60 minutes, you could create a timer that wakes up every 60 minutes and if the time is up, it calls the method.

Something like this:

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;

and then in your event handler:

void checkForTime_Elapsed(object sender, ElapsedEventArgs e)
{
    if (timeIsReady())
    {
       SendEmail();
    }
}
Eisk answered 14/7, 2010 at 4:16 Comment(6)
Thought about it before. :). But my program will run continuously, and I want to know an alternative way, if there is :)Grappling
it's a Winform app. I'll try to convince my boss to change it design, but at first I should try to fill his requirements :pGrappling
Thank you for your answer. I added some information. I'm much appreciated if you can help ...Grappling
What does the timeIsReady() call do?Redvers
@brimble2010: it can do anything you like it to do. You could e.g. have a table with temporarily "blocked" timeslots (e.g. don't run at 3, 4, or 5 in the morning) or whatever - totally up to you. Just an additional check, on top of being launched every 60 minutes ....Eisk
Very nice, simple, short and clean solution which working excellent until today in Windows 10 & .Net 4.7.2.!! Thank you.Calumniation
W
30

I created a simple scheduler that is easy to use and you do not need to use external library. TaskScheduler is a singleton that keeps references on the timers so timers will not be garbage collected, it can schedule multiple tasks. You can set the first run (hour and minute), if at the time of scheduling this time is over scheduling start on the next day this at that time. But it is easy to customize the code.

Scheduling a new task is so simple. Example: At 11:52 the first task is for every 15 secunds, the second example is for every 5 secunds. For daily execution set 24 to the 3 parameter.

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

My debug window:

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

Just add this class to your project:

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);
    }
}
Windhover answered 7/6, 2017 at 10:45 Comment(4)
new Timer does not have a method that takes 4 arguments.Kirshbaum
I assume Timer here is from System.Timers? Could you provide a sample working program? ThanksKirshbaum
@Kirshbaum I have used the Timer from namespace System.Threading. There is another Timer in System.Timers. I think you have used the false using on the top.Windhover
@jannagy02 How to schedule a task for a particular day like Monday?Unmoved
B
9

Whenever I build applications that require such functionality, I always use the Windows Task Scheduler through a simple .NET library that I found.

Please see my answer to a similar question for some sample code and more explanation.

Bianco answered 14/7, 2010 at 6:9 Comment(1)
This library is only available for Windows OS, if you need to run it on linux or some other OS, please do not use it in your .NET Core project.Defence
F
8

As others have said you can use a console app to run when scheduled. What others haven't said is that you can this app trigger a cross process EventWaitHandle which you are waiting on in your main application.

Console App:

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

Main App:

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();
    }
}
Forland answered 14/7, 2010 at 6:2 Comment(0)
S
8

Here's a way to do this using TPL. No need to create/dispose of a timer, etc:

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
}
Stevens answered 28/11, 2017 at 3:17 Comment(0)
P
4

The best method that I know of and probably the simplest is to use the Windows Task Scheduler to execute your code at a specific time of day or have you application run permanently and check for a particular time of day or write a windows service that does the same.

Periostitis answered 14/7, 2010 at 4:17 Comment(0)
C
4

I know this is old but how about this:

Build a timer to fire at startup that calculates time to next run time. At the first call of the runtime, cancel the first timer and start a new daily timer. change daily to hourly or whatever you want the periodicity to be.

Caelian answered 17/11, 2011 at 16:0 Comment(1)
And watch it fail during Daylight Saving Time changes . . .Siusan
U
3

This little program should be the solution ;-)

I hope this helps everyone.

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();
            }
        }
    }
}
Uzzia answered 21/2, 2020 at 15:13 Comment(0)
M
2

I just recently wrote a c# app that had to restart daily. I realize this question is old but I don't think it hurts to add another possible solution. This is how I handled daily restarts at a specified time.

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

To ensure your timer is kept in scope I recommend creating it outside of the method using System.Timers.Timer tmrRestart = new System.Timers.Timer() method. Put the method RestartApp() in your form load event. When the application launches it will set the values for AppRestart if current is greater than the restart time we add 1 day to AppRestart to ensure the restart happens on time and that we don't get an exception for putting a negative value into the timer. In the tmrRestart_Elapsed event run whatever code you need ran at that specific time. If your application restarts on it's own you don't necessarily have to stop the timer but it doesn't hurt either, If the application does not restart simply call the RestartApp() method again and you will be good to go.

Mikol answered 19/10, 2016 at 13:14 Comment(0)
T
2

How about a 3 liner?

        DateTime startTime = DateTime.Today.AddDays(1).AddHours(8).AddMinutes(30); // Today starts at midnight, so add the number of days, hours and minutes until the desired start time, which in this case is the next day at 8:30 a.m.
        TimeSpan waitFor = startTime - DateTime.Now; // Calcuate how long it is until the start time
        await Task.Delay(waitFor); // Wait until the start time
Trinomial answered 1/4, 2021 at 21:38 Comment(1)
this answer does not add any new idea to the already present answersDeste
S
1

I found this very useful:

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();
        }
    }
}
Saberio answered 15/4, 2019 at 15:12 Comment(0)
S
0

If you want an executable to run, use Windows Scheduled Tasks. I'm going to assume (perhaps erroneously) that you want a method to run in your current program.

Why not just have a thread running continuously storing the last date that the method was called?

Have it wake up every minute (for example) and, if the current time is greater than the specified time and the last date stored is not the current date, call the method then update the date.

Sticky answered 14/7, 2010 at 4:18 Comment(0)
B
0

Rather than setting a time to run every second of every 60 minutes you can calculate the time remaining and set the timer to half (or some other fraction) of this. This way your not checking the time as much but also maintianing a degree of accurcy as the timer interval reduces the closer you get to your target time.

For example if you wanted to do something 60 minutes from now the timers intervals would be aproximatly:

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

I use the code below to automatically restart a service once a day. I use a thread becuase I have found timers to be unreliable over long periods, while this is more costly in this example it is the only one created for this purpose so this dosn't matter.

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

Note I have set a mininum interval of 1000 ms, this could be increaded, reduced or removed depending upon the accurcy you require.

Remember to also stop your thread/timer when your application closes.

Birkner answered 18/12, 2015 at 11:11 Comment(0)
G
0

It may just be me but it seemed like most of these answers were not complete or would not work correctly. I made something very quick and dirty. That being said not sure how good of an idea it is to do it this way, but it works perfectly every time.

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);
}
Grizelda answered 16/12, 2016 at 21:40 Comment(1)
As far as I can see this would be a busy wait and therefore not a good idea. en.wikipedia.org/wiki/Busy_waitingAssert
B
0

I have a simple approach to this. This creates a 1 minute delay before the action happens. You could add seconds as well to make the Thread.Sleep(); shorter.

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
        }
    }
}
Burns answered 5/1, 2017 at 10:33 Comment(0)
V
0

24 hours times

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";
    }
Viridissa answered 26/6, 2018 at 11:33 Comment(0)
A
0

Try to use Windows Task Scheduler. Create an exe which is not prompting for any user inputs.

https://learn.microsoft.com/en-us/windows/win32/taskschd/task-scheduler-start-page

Arnst answered 20/11, 2019 at 7:6 Comment(0)
A
0

A simple example for one task:

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();
        }
    }
}
Aniline answered 10/11, 2020 at 15:28 Comment(3)
This doesn't looks like a timer, instead it looks like a interval, not accurate.Rovelli
Timer start time is set in a variable scheduledTime. The method Console.WriteLine("Time to run task") will run every day at this time.Aniline
Thanks for the explanation.Rovelli
P
-1

Solution with 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);
    }
Propound answered 13/11, 2019 at 7:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.