6

I need help. I have Windows Service and I need run this service every hour in specific minute for example: 09:05, 10:05, 11:05,.... My service now start every hour but every hour from time when i start this service. So how can I achieve my needs.

My code:

public partial class Service1 : ServiceBase
{
    System.Timers.Timer timer = new System.Timers.Timer();

    public Service1()
    {
        InitializeComponent();
    }

    protected override void OnStart(string[] args)
    {
        this.WriteToFile("Starting Service {0}");

        timer.Elapsed += new ElapsedEventHandler(OnElapsedTime);

        timer.Interval = 60000;

        timer.Enabled = true;
    }

    protected override void OnStop()
    {
        timer.Enabled = false;

        this.WriteToFile("Stopping Service {0}");
    }

    private void OnElapsedTime(object source, ElapsedEventArgs e)
    {
        this.WriteToFile(" interval start {0}");
    } }
Marko
  • 227
  • 2
  • 6
  • 13
  • So, what happens? You appear to have written code that matches the spec you've outlined. And you've not told us anything else, such as: an error? unexpected behaviour? Your machine achieved sentience and has started plotting world domination? – Damien_The_Unbeliever Mar 27 '17 at 07:51
  • This might help: http://stackoverflow.com/questions/21299214/how-to-set-timer-to-execute-at-specific-time-in-c-sharp – Dave Becker Mar 27 '17 at 07:51
  • @Damien_The_Unbeliever this happens: "My service now start every hour but every hour from time when i start this service" OP wants it to fire on a specific time not an hour after the service starts. – Dave Becker Mar 27 '17 at 07:53
  • I think the logic will be along the lines of: fire the timer event more often that you need to and check `DateTime.Now.Minute == 5` in the handler – Dave Becker Mar 27 '17 at 07:55
  • 1
    Looks too similar to a scheduled task. Check Quartz or consider making a simple console app that would be run as a windows scheduled task. – Cleptus Mar 27 '17 at 08:03
  • @bradbury9 I have had a somewhat similar situation and I used a method like my answer. It is robust and timers fire just as expected. I tried scheduled tasks it is not as controllable and exact. – Emad Mar 27 '17 at 08:04

4 Answers4

10

You should check current time every 'n' seconds (1 as example) from timer:

public partial class Service1 : ServiceBase
{
    System.Timers.Timer timer = new System.Timers.Timer();

    public Service1()
    {
        InitializeComponent();
    }

    protected override void OnStart(string[] args)
    {
        this.WriteToFile("Starting Service {0}");

        timer.Elapsed += new ElapsedEventHandler(OnElapsedTime);

        timer.Interval = 1000; // 1000 ms => 1 second

        timer.Enabled = true;
    }

    protected override void OnStop()
    {
        timer.Enabled = false;

        this.WriteToFile("Stopping Service {0}");
    }

    private int lastHour = -1;
    private void OnElapsedTime(object source, ElapsedEventArgs e)
    {
        var curTime = DateTime.Now; // Get current time
        if (lastHour != curTime.Hour && curTime.Minute == 5) // If now 5 min of any hour
        {
            lastHour = curTime.Hour;

            // Some action
            this.WriteToFile(" interval start {0}");
        }
    } 
}
Ivan Kishchenko
  • 795
  • 4
  • 15
  • This is a good answer with only a small flaws. This will rise every minute while you don't need it and also it will not be exact on the minute for example if it is 12:04:36 now it will run the process at 01:05:36 while it can do it in 01:05:00 – Emad Mar 27 '17 at 08:00
  • I agree with you. More accuracy can be achieved by reducing the timer interval. I edited the example. Now the time infelicity is 1 second. If necessary, you can do even less or more. – Ivan Kishchenko Mar 27 '17 at 08:09
  • But a service needs to run every hour should it poll the clock every second! Check out my answer and that does the same. – Emad Mar 27 '17 at 08:11
  • @Emad , With continued operation, your example will give an unmanageable time shift. You need to bind to the system time as in my example. – Ivan Kishchenko Mar 27 '17 at 08:20
  • @IvanKishchenko - are you thinking that the `OnElapsedTime` event will only run when the previous cycle has finished? Because that is not the case. The services will start multiple threads running `OnElapsedTime` at the same time if necessary. If the interval is set to `1` minute and it takes `2` minutes to run, then in the second minute you will have two instances of it running. There should not be a "time shift" – Rufus L Mar 27 '17 at 09:18
  • @Rufus L - If the timer was started at 00:00:00.0000 (HH:mm:ss.ffff) and the interval is set to 1 minute, then after 1 hour of operations the method will not be executed at 01:00:00.0000, 01:01:00.0000, 01:02:00.0000 etc. as expected, but at 01:00:05.1754, 01:01:05.1783, 01:02:05.1796 and etc. Increasing the error against the background of the total time. – Ivan Kishchenko Mar 27 '17 at 09:43
2

Here's another way you can calculate the first interval:

var minutesAfterHourToStart = 5;  // Start at 5 minutes after the hour

var now = DateTime.Now;
var minutesToStart = 60 - (now.Minute - (minutesAfterHourToStart - 1));
if (minutesToStart > 60) minutesToStart -= 60;
var secondsToStart = 60 - now.Second + (minutesToStart * 60);

timer.Interval = TimeSpan.FromSeconds(secondsToStart).TotalMilliseconds;

Then, in the OnTimeElapsed event, you would set the interval to run every hour. To prevent constantly setting the variable to the same value over and over, we could set a global variable that we've already set the final interval:

class MyService
{
    private bool resetInterval = true;

Then we can check this and set it to false the first time through:

private void OnElapsedTime(object source, ElapsedEventArgs e)
{
    if (resetInterval)
    {
        timer.Interval = TimeSpan.FromHours(1).TotalMilliseconds;
        resetInterval = false;
    }
Rufus L
  • 36,127
  • 5
  • 30
  • 43
  • After a long work, the timer can give an error between the iterations, since the total time is added to the time between iterations, so a serious error may appear in the long work. Please see my comment to Emad's answer. As variant for fix it, you can set 'timer.Interval' in 'OnElapsedTime' to difference between next hour datetime and current time. – Ivan Kishchenko Mar 27 '17 at 09:10
  • @IvanKishchenko - I don't understand what you mean by `the total time is added to the time between iterations`. The interval gets set two times - once at service start, and once in `OnElapsedTime`. And once you start the `Timer`, the code will run every hour, regardless of how long the work is. The timer does not wait for a previous cycle to finish before it starts a new cycle. – Rufus L Mar 27 '17 at 09:16
  • I did not mean it. If the timer was started at 00:00:00.0000 (HH:mm:ss.ffff) and the interval is set to 1 minute, then after 1 hour of operations the method will not be executed at 01:00:00.0000, 01:01:00.0000, 01:02:00.0000 etc. as expected, but at 01:00:05.1754, 01:01:05.1783, 01:02:05.1796 and etc. Increasing the error against the background of the total time. – Ivan Kishchenko Mar 27 '17 at 09:32
  • 1
    Wow, I ran a test and you're right. `System.Timer` is horrible. – Rufus L Mar 27 '17 at 21:00
0

You have all you need just start the timer differently:

public partial class Service1 : ServiceBase
{
    System.Timers.Timer timer = new System.Timers.Timer();

    public Service1()
    {
        InitializeComponent();
    }

    protected override void OnStart(string[] args)
    {
        this.WriteToFile("Starting Service {0}");

        timer.Elapsed += new ElapsedEventHandler(OnElapsedTime);
        var excess = DateTime.Now.Minute - 5;
        var span = DateTime.Now.AddMinutes(excess <= 0 ? -excess : 60- excess) - DateTime.Now;
        timer.Interval = span.TotalMilliseconds;

        timer.Enabled = true;
    }

    protected override void OnStop()
    {
        timer.Enabled = false;

        this.WriteToFile("Stopping Service {0}");
    }

    private void OnElapsedTime(object source, ElapsedEventArgs e)
    {
        var excess = DateTime.Now.Minute - 5;
        var span = DateTime.Now.AddMinutes(excess <= 0 ? -excess : 60- excess) - DateTime.Now;
        timer.Interval = span.TotalMilliseconds;
        this.WriteToFile(" interval start {0}");
    } 
}

This way you set for the first "x:05" minute and then every hour.

Emad
  • 3,809
  • 3
  • 32
  • 44
  • I'm not sure this works. It's `13:08` here right now, and this returns a span of `-3`, which creates an interval of `0` – Rufus L Mar 27 '17 at 08:10
  • No it checks `excess <= 0 ? excess + 60 : excess` – Emad Mar 27 '17 at 08:11
  • And what happens if windows/the user realises that the clock is wrong by a few minutes and adjusts it whilst your service is running? – Damien_The_Unbeliever Mar 27 '17 at 08:15
  • After a long work, the timer can give an error between the iterations, since the total time is added to the time between iterations, so a serious error may appear in the long work. To avoid this, you need to be tied to a particular time in the system. (See my example) – Ivan Kishchenko Mar 27 '17 at 08:16
  • 1
    @Emad - I wasn't guessing - that is what it returned. For `13:08`, excess is `3`. Then you do `Now - (Now + 3 minutes)`, which equals a **negative** number. A negative span has `0` `Milliseconds` – Rufus L Mar 27 '17 at 08:23
  • @Emad - check your logic. You taking two datetimes and subtracting them. Your logic is ensuring that the second datetime is always equal or later to the first. So that subtracting will *always* generate a negative or zero timespan. – Damien_The_Unbeliever Mar 27 '17 at 08:26
  • @RufusL Yes, you are right. It was me who didn't pay attention :) I have editted the answer also this time I fixed the argument of slight shifting over time. – Emad Mar 27 '17 at 08:27
  • 1
    @Emad - It looks like your logic is still off. Right now it's `13:42`. `excess` is therefore `38`. Your `span` line is redundant, because it takes `Now`, adds `38` minutes, then subtracts `Now` again, so the `Interval` becomes `38` minutes, which means the timer will run at `14:20` – Rufus L Mar 27 '17 at 08:44
  • @RufusL It's funny how some easy logic became that hard to do for me! I corrected it yet again. Thanks for your attention. – Emad Mar 27 '17 at 08:54
  • 1
    @Emad - No problem. I missed something before, though - you should be calling `span.TotalMilliseconds` when you're setting the `Interval`. That's why it was returning `0` before. `Milliseconds` is just the millisecond portion of the span, `TotalMilliseconds` converts the whole thing to milliseconds. – Rufus L Mar 27 '17 at 09:07
0

Maybe just leave your code like it is, and set your service to start manually, then use windows scheduler to start it one time at specific hour. The rest work will do timer and OnElapse method; This could help you: https://stackoverflow.com/a/36309450/5358389

Community
  • 1
  • 1
daniell89
  • 1,832
  • 16
  • 28