2

I would like to run code conditionally based on the time of day. The code is within a while loop in several worker tasks that run throughout my programs lifetime. Performing the comparison on every loop iteration seems wasteful, is there a more efficient way to get this desired result?

The restate my question in code, I am asking if there is a more efficient to duplicate this functionality, perhaps using timers or some other scheduling mechanism:

while( workerNotCanceled )
{
    var time = DateTime.Now;
    if (time.Hour > 8 and time.Hour < 16)
        DoWork();
}
cubesnyc
  • 1,385
  • 2
  • 15
  • 32
  • 2
    You could use a job scheduler like https://www.quartz-scheduler.net/ – richej Oct 07 '20 at 05:31
  • 1
    Use task scheduler on windows or crontab on linux – shingo Oct 07 '20 at 06:50
  • @shingo ... to do what? – Fildor Oct 07 '20 at 06:50
  • 1
    @Fildor to do work on a new process or send some signal to the main process, though I don't know what kind of work the OP wants to do. – shingo Oct 07 '20 at 06:56
  • @shingo It's a bit vague, yes. That's why I am afraid only suggesting to use some tool X is not enough information but it is also hard to get more detailed in an answer... :( – Fildor Oct 07 '20 at 07:09
  • Take a look at the `Timer.Elapsed` event, maybe it's useful – Essigwurst Oct 07 '20 at 07:29
  • @Essigwurst Why do think? OP doesn't want equidistant time spans. He wants distinct instants in time of the day. A scheduler is _much_ more appropriate. – Fildor Oct 07 '20 at 07:30

2 Answers2

0

I don't know what DoWork() does, but it's quite probable that the time comparison is neglectable compared to it. You will only have a lot of time comparisons after 16:00 o'clock and before 8:00 o'clock. If the loop is entered outside the time frame, you could block the thread until it should do its work. If it is after 16:00 o'clock, it will sleep until the next day 8:00 o'clock, if it's before 8 o'clock, it will sleep until the same day 8:00 o'clock. Note that when you use Thread.Sleep you will be unable to cancel the loop outside the working time frame. If you want to do this, you can use a cancellation token.

while( workerNotCanceled )
{
    var time = DateTime.Now;
    if (time.Hour > 8 and time.Hour < 16)
        DoWork();
    else if(time.Hour >= 16)
    {
       DateTime nextStart = DateTime.Now.Date.AddDays(1).AddHours(8);
       Thread.Sleep(nextStart - DateTime.Now);
    }
    else
    {
       DateTime nextStart = DateTime.Now.Date.AddHours(8);
       Thread.Sleep(nextStart - DateTime.Now);
    }
}
SomeBody
  • 7,515
  • 2
  • 17
  • 33
  • Young man! You have been instantly disqualified from this competition. No `Thread.Sleep()` in any realistic code! – Tanveer Badar Oct 07 '20 at 07:27
  • 1
    ^^ Harsh love ... :D But I have to agree with Tanveer. – Fildor Oct 07 '20 at 07:28
  • 1
    Old man! That's why I linked to the question with the cancellation token! – SomeBody Oct 07 '20 at 07:30
  • 1
    @SomeBody `Thread.Sleep()` in real code is bad in all flavors, cancellation token or not. Given the workload, even an `await Task.Delay()` will not be appropriate, even though either variety will appear to get the job done. OP should really be looking at means to use OS provided scheduling facilities. – Tanveer Badar Oct 07 '20 at 07:55
  • @TanveerBadar I don't think the workload is quite high, because when the program is running all the time, it will be called once in the afternoon and then wait 16 hours. I know `Thread.Sleep()` is not recommended in most cases, but I wanted to keep the example as simple as possible and I provided a link with better alternatives. And this solution has the big advantage that OP does not have to make severe changes of his code. – SomeBody Oct 08 '20 at 05:23
0

So, I figure there would be different ways to approach this. If I had to do it, I probably would go for some variation of the strategy pattern and divide & conquer.

Divide & Conquer:

Separate switching / time checking from the actual job. So, I'd find some way to exchange a "do the job" strategy to a "do nothing" or "drop job" strategy.

That could be done using Quartz or a similar scheduling framework inside the app, which would trigger "switch off" and "switch on" jobs at the appropriate times.

Same could be done with cron or windows task scheduler which could trigger an api in your app. This opens up an attack vector, though.

Strategy pattern

That's relatively simple here: You'd have an interface with two implementations. The "switch on/off" jobs then simply "plug in" the appropriate implementation.

Example:

interface IWorker{
     void DoWork(); // maybe with an argument
}

class ActiveWorker : IWorker
{
     public void DoWork()
     {
          workerService.DoWork(); // replace with whatever is appropriate for you.
     }
}

class InactiveWorker : IWorker
{
    public void DoWork()
    {
         // Maybe just do nothing?
         // Maybe actively drop a request?
    }
}

In your consumer, you'd then have

IWorker worker; // Setup initially based on DateTime.Now

void ConsumingLooper()
{
     //...
     worker.DoWork(); // Based on which impl. is set to 'worker' will
                      // either handle or drop
}

Don't forget to add measures to handle the case where the looper wants to call worker.DoWork() while it is switched out. I left it out for brevity and also there are many different ways to achieve it. You may want to pick your favorite.

Fildor
  • 14,510
  • 4
  • 35
  • 67