1

I want to execute an .exe every morning at 4:00 AM. I did this using following code.

  while (true)
  {
      var now = DateTime.Now;
      var schedule = new DateTime(now.Year, now.Month, now.Day, 4, 00, 00);
      if (schedule < now) schedule = schedule.AddDays(1);

      Thread.Sleep(schedule.Subtract(now));

      Process.Start("sample.exe");
  }

Now I want to execute one more tasks, like deleting a particular folder and subfolders every night at 1:00 AM. So for deleting I will use code like

Directory.Delete(folder_path, recursive: true);

Should I use the same infinite loop or achieve it in some other way?

piedar
  • 2,599
  • 1
  • 25
  • 37
peter
  • 8,158
  • 21
  • 66
  • 119
  • Are you forced to use Thread.Sleep or didn't you find the Timer classes? – rene Jan 15 '17 at 18:21
  • If you can use Tasks maybe use [Delay](http://stackoverflow.com/questions/13429707/how-to-get-awaitable-thread-sleep?rq=1) and have multiple Task with their own Delay set. – rene Jan 15 '17 at 18:23
  • i feel this approch is simpler – peter Jan 15 '17 at 18:23
  • When you wanted to only run one task that was probably true ... ;) – rene Jan 15 '17 at 18:25
  • what you meant,do you have any alternate way to run both – peter Jan 15 '17 at 18:26
  • I guess it can work in this construct but you'll need to to Sleep until one of your tasks is due and then execute that task. If you have only two tasks to run, just use a Timer for each, setting them at the right interval. – rene Jan 15 '17 at 18:29
  • 3
    Create a windows service and let the task scheduler manage when your program executes your desired behavior. why reinvent the wheel – Nkosi Jan 15 '17 at 18:31
  • 2
    Agree with @Nkosi. For a computer to schedule, it'll have to keep checking the time, and once the time is appropriate, perform some task. You can create time-checking loops yourself, but the OS's scheduler is already running anyway, plus it should have a lot of reliability and efficiency. That said, if you want to run your own threads for fun, the code's in my answer. – Nat Jan 15 '17 at 18:41
  • 2
    You might want to look at something like [Quartz](http://www.quartz-scheduler.net/documentation/faq.html). What do you expect when some government decides to throw in a 23 or 25 hour day? Scheduling is rarely as simple as it seems. Stupid leap seconds. – HABO Jan 15 '17 at 19:13
  • Can you use the Windows Task Scheduler? – EJoshuaS - Stand with Ukraine Jan 16 '17 at 00:47

4 Answers4

4

You shouldn't use Thread.Sleep for timing. Try to avoid Thread.Sleep. You can only interrupt a sleep if you abort that thread. A better way would be a ManualResetEvent.Wait() which can be interrupted by setting it.


My advise is, use a timer:

Create a timer that is checking a list of jobs. Add the next time to execute on the job. A timer won't block the MainThread, so you can safely terminate the application. You could check the Jobs (interval) each minute or even higher.

PSEUDO!!

class MyJob
{
    public DateTime ExecuteTime { get; set; }
    public Action Action { get; set; }
}

List<MyJob> Jobs = new List<MyJob>();

public void TimerCallback(object sender, EventArgs e)
{
    foreach (var job in Jobs)
    {
        if (job.ExecuteTime <= DateTime.Now)
        {
            try
            {
                job.Action();
            }
            catch(Exception exception)
            {
                // log the exception..
            }
            finally
            {
                job.ExecuteTime = job.ExecuteTime.Add(TimeSpan.FromDays(1));
            }
        }
    }
}

// this AddJob checks if the first execute should be today or tomorrow
public void AddJob(TimeSpan executeOn, Action action)
{
    var now = DateTime.Now;

    var firstExec = new DateTime(now.Year, now.Month, now.Day, 4, 0, 0);

    if (firstExec < now)  // time for today is already passed, next day..
        firstExec = firstExec.Add(TimeSpan.FromDays(1));

    Jobs.Add(new MyJob { ExecuteTime = firstExec, Action = action });
}

public void Add()
{
    // Add the jobs.
    AddJob(new TimeSpan(4, 0, 0), RunMyJob);
    AddJob(new TimeSpan(5, 0, 0), RunMyJob2);
}

public void RunMyJob()
{
    // delete or move, whatever...
}

public void RunMyJob2()
{
    // delete or move, whatever...
}
Jeroen van Langen
  • 21,446
  • 3
  • 42
  • 57
  • I tagged my question as console application,let me see thiswill work – peter Jan 15 '17 at 19:15
  • @rene like it says, it's pseudo. He might use a threading timer: look here: [How do you add a timer to a C# console application](http://stackoverflow.com/questions/186084/how-do-you-add-a-timer-to-a-c-sharp-console-application). I just gave an example. He should do the implementation;-) – Jeroen van Langen Jan 15 '17 at 19:19
  • @peter look at the link in the previous comment – Jeroen van Langen Jan 15 '17 at 19:19
2

If all you're doing is scheduling a single .exe, you can use the Windows Task Scheduler - it's a lot easier than trying to do it yourself. If you need several tasks done at several different times, you can create several .exes. (Sorry to be brief, I'm writing this on a phone).

2

I would use Microsoft's Reactive Framework (NuGet "System.Reactive") for this.

You can then write this handy helper method:

public IDisposable ScheduleDaily(TimeSpan when, Action what)
{
    var now = DateTime.Now;
    var schedule = now.Date.Add(when);
    if (schedule < now)
    {
        schedule = schedule.AddDays(1.0);
    }

    return
        Observable
            .Timer(schedule, TimeSpan.FromDays(1.0))
            .Subscribe(_ => what());
}

To call it just do this:

IDisposable runSample = ScheduleDaily(TimeSpan.FromHours(4.0),
    () => Process.Start("sample.exe"));

IDisposable deleteFolders = ScheduleDaily(TimeSpan.FromHours(1.0),
    () => Directory.Delete(folder_path, recursive: true));

The ScheduleDaily returns a handy IDisposable that you can .Dispose() to stop the timer.

No threads or sleeping required. Very simple.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
1

Thread.Sleep() causes the current thread to sleep, so that thread won't be doing anything again until it's done sleeping (unless you interrupt it or something).

So, if you do it like that, you just need more threads.

var scheduleLoopAction = new Action<int, int, string>((int hour, int minute, string processName) =>
{
    while (true)
    {
        var now = DateTime.Now;
        var schedule = new DateTime(now.Year, now.Month, now.Day, hour, minute, 00);
        if (schedule < now) schedule = schedule.AddDays(1);

        System.Threading.Thread.Sleep(schedule.Subtract(now));

        System.Diagnostics.Process.Start(processName);
    }
});

((new System.Threading.Thread(() => { scheduleLoopAction(4, 0, "sample.exe"); }))).Start();
((new System.Threading.Thread(() => { scheduleLoopAction(1, 0, "sample2.exe"); }))).Start();

This probably isn't the best way accomplish the goal, but I figured that you'd wanted something like what was in the question.

Note that the thread calls don't block, so this method will finish right away (unlike the sample in the question, which'll go forever). If you want it to block, you can just have the last schedule run in the current thread, e.g.

((new System.Threading.Thread(() => { scheduleLoopAction(4, 0, "sample.exe"); }))).Start();
scheduleLoopAction(1, 0, "sample2.exe");  // Runs in current thread
Nat
  • 1,085
  • 2
  • 18
  • 35