44

I want to do stuff every minute on the minute (by the clock) in a windows forms app using c#. I'm just wondering whats the best way to go about it ?

I could use a timer and set its interval to 60000, but to get it to run on the minute, I would have to enable it on the minute precisely, not really viable.

I could use a timer and set its interval to 1000. Then within its tick event, I could check the clocks current minute against a variable that I set, if the minute has changed then run my code. This worries me because I am making my computer do a check every 1 second in order to carry out work every 1 minutes. Surely this is ugly ?

I'm using windows forms and .Net 2.0 so do not want to use the DispatchTimer that comes with .Net 3.5

This must be a fairly common problem. Have any of you a better way to do this?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • Did you ever find an elegant solution to this? I'm running into a similar problem where I want to update a clock on a WPF application. – MGSoto Jun 22 '10 at 18:07
  • Somewhat related: [“timer + Task.Run” vs “while loop + Task.Delay” in asp.net core hosted service](https://stackoverflow.com/questions/64517214/timer-task-run-vs-while-loop-task-delay-in-asp-net-core-hosted-service). It's about scheduling every midnight instead of every minute though. – Theodor Zoulias Jun 10 '21 at 12:36

15 Answers15

58

Building on the answer from aquinas which can drift and which doesn't tick exactly on the minute just within one second of the minute:

static System.Timers.Timer t;

static void Main(string[] args)
{
    t = new System.Timers.Timer();
    t.AutoReset = false;
    t.Elapsed += new System.Timers.ElapsedEventHandler(t_Elapsed);
    t.Interval = GetInterval();
    t.Start();
    Console.ReadLine();
}

static double GetInterval()
{
    DateTime now = DateTime.Now;
    return ((60 - now.Second) * 1000 - now.Millisecond);
}

static void t_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    Console.WriteLine(DateTime.Now.ToString("o"));
    t.Interval = GetInterval();
    t.Start();
}

On my box this code ticks consistently within .02s of each minute:

2010-01-15T16:42:00.0040001-05:00
2010-01-15T16:43:00.0014318-05:00
2010-01-15T16:44:00.0128643-05:00
2010-01-15T16:45:00.0132961-05:00
Jared
  • 1,305
  • 9
  • 13
  • 2
    Why the down vote? I think this is a very good solution which will be pretty precise even after days or weeks! – gehho Jul 19 '10 at 06:49
  • 8
    elapsed may occur slightly before 00s, which will trigger elapsed twice. Corrected by: `return ((now.Second > 30 ? 120 : 60) - now.Second) * 1000 - now.Millisecond;` – bretddog Feb 21 '13 at 13:46
  • 1
    @bretddog I guess the best way to ensure this is to keep a DateTime inited to nearest full minute which you increase by one minute every tick. Then can calculate the interval correctly as `(Next-DateTime.Now).TotalMilliseconds`. Just my two cents :) Thanks anyway this thread helped me a lot. – Christoph Diegelmann May 07 '14 at 10:50
  • @bretddog Why it may occur slightly before 00s? I keep thinking but not getting it. – Gaurav Jan 15 '19 at 03:18
  • @Gaurav I'm quite sure I determined this from testing only, as I used this method for an alert-feature. I believe observation was that positive drift was the norm, but negative drift occurred occasionally. Thinking now, one cause could be Windows-system-time synchronization, but I can't remember if that was the case I observed. (no notes in my code to why). – bretddog Jan 15 '19 at 12:39
30

How about:

int startin = 60 - DateTime.Now.Second;
var t = new System.Threading.Timer(o => Console.WriteLine("Hello"), 
     null, startin * 1000, 60000);
aquinas
  • 23,318
  • 5
  • 58
  • 81
  • 2
    +1: That's a nice way of dealing with the drift of a 60 second timer. – Walt W Aug 25 '09 at 18:24
  • 2
    It's not dealing with the drift ... the drift is about -1 millisecond in 8 seconds (on my machine). So there has to be a way to resync it. – Bdiem Aug 26 '09 at 12:59
  • 1
    I should have put a warning on this. If you REQUIRE the event to happen within the exact right second (on the minute in this case), then my solution should not be used and the only possible solution is to check every second. It's a *tiny* overheard. With my solution, suppose that it starts at 1:00.00.999, and then you tell it to fire again 1 minute later, there is a HIGH probability that the event won't fire EXACTLY 60000 milliseconds later. Ok, it probably will the first few times, but guaranteed you are losing ticks here and there. I did think this solution (in the abstract) was clever:) – aquinas Aug 26 '09 at 15:27
  • 1
    DateTime d = DateTime.Now; int startin = 60000 - (d.Second*1000) - d.Millisecond; var t = new System.Threading.Timer(o => Console.WriteLine("Hello"), null, startin, 60000); – richardtallent Sep 03 '09 at 22:16
  • @aquinas I used your solution. Its working for 23 minitues. But then its not working. Is garbage collector stopping it? Please check this question. I asked this. https://stackoverflow.com/questions/64861991/system-threading-timer-stop-after-certain-time-without-errors-how-to-solve-this – Prageeth Liyanage Nov 16 '20 at 16:42
  • @PrageethLiyanage yes. If it's a local variable it's getting GCd. – aquinas Nov 17 '20 at 13:37
9

Creating a Timer control that fires every 1 second (and usually does nothing but a simple check) will add negligible overhead to your application.

Simply compare the value of Environment.TickCount or DateTime.Now to the last stored time (the previous 'minute tick'), and you should have a reasonably precise solution. The resolution of these two time values is about 15ms, which should be sufficient for your purposes.

Do note however that the interval of the Timer control is not guaranteed to be that precise or even anywhere now, since it runs on the Windows message loop, which is tied in with the responsiveness of the UI. Never rely on it for even moderately precise timing - though it is good enough for firing repeating events where you can check the time using a more sensitive method such as one of the two given above.

Noldorin
  • 144,213
  • 56
  • 264
  • 302
  • I always assumed this just started up a background thread that marshaled events up to the UI thread periodically, but it looks like it calls into the native Windows UI. – Jason Jackson Aug 25 '09 at 18:12
  • @Jason: Yes, it's purely controlled by the message pump (I'm pretty confident). Fairly bad resolution, but no performance hit at least. – Noldorin Aug 25 '09 at 18:21
9

You can nail this with reactive extensions which will take care of lots of timer related problems for you (clock changes, app hibernation etc). Use Nuget package Rx-Main and code like this:

Action work = () => Console.WriteLine(DateTime.Now.ToLongTimeString());

Scheduler.Default.Schedule(
    // start in so many seconds
    TimeSpan.FromSeconds(60 - DateTime.Now.Second), 
    // then run every minute
    () => Scheduler.Default.SchedulePeriodic(TimeSpan.FromMinutes(1), work));               

Console.WriteLine("Press return.");
Console.ReadLine();

Read here (search for "Introducing ISchedulerPeriodic") to see all the issues this is taking care of: http://blogs.msdn.com/b/rxteam/archive/2012/06/20/reactive-extensions-v2-0-release-candidate-available-now.aspx

James World
  • 29,019
  • 9
  • 86
  • 120
5

Create a method or put this code where you want the timer to start:

 int time = 60 - DateTime.Now.Second; // Gets seconds to next minute
        refreshTimer.Interval = time * 1000;
        refreshTimer.Start();

And then on your tick event set the interval to 60000:

  private void refreshTimer_Tick(object sender, EventArgs e)
    {
        refreshTimer.Interval = 60000; // Sets interval to 60 seconds
        // Insert Refresh logic
    }
Ste Brown
  • 109
  • 1
  • 3
5

I jsut wrote this class using the WPF DispatcherTimer but you can swap the dispatcher for any timer that supports changing when it's woken from sleep state.

The class is constructed with a fixed time step and supprts Start/Stop/Reset, Start/Stop/Start works like a resume operation. The timer is like a stopwatch in that regard.

A clock implementation would simply create the class with a interval of 1 second and listen to the event. Be wary though that this is a real-time clock, if the tick event takes longer than the interval to finish you'll notice that the clock will try and catch up to real-time this will cause a burst of tick events being raised.

public class FixedStepDispatcherTimer
{
    /// <summary>
    /// Occurs when the timer interval has elapsed.
    /// </summary>
    public event EventHandler Tick;

    DispatcherTimer timer;

    public bool IsRunning { get { return timer.IsEnabled; } }

    long step, nextTick, n;

    public TimeSpan Elapsed { get { return new TimeSpan(n * step); } }

    public FixedStepDispatcherTimer(TimeSpan interval)
    {
        if (interval < TimeSpan.Zero)
        {
            throw new ArgumentOutOfRangeException("interval");
        }
        this.timer = new DispatcherTimer();
        this.timer.Tick += new EventHandler(OnTimerTick);
        this.step = interval.Ticks;
    }

    TimeSpan GetTimerInterval()
    {
        var interval = nextTick - DateTime.Now.Ticks;
        if (interval > 0)
        {
            return new TimeSpan(interval);
        }
        return TimeSpan.Zero; // yield
    }

    void OnTimerTick(object sender, EventArgs e)
    {
        if (DateTime.Now.Ticks >= nextTick)
        {
            n++;
            if (Tick != null)
            {
                Tick(this, EventArgs.Empty);
            }
            nextTick += step;
        }
        var interval = GetTimerInterval();
        Trace.WriteLine(interval);
        timer.Interval = interval;
    }

    public void Reset()
    {
        n = 0;
        nextTick = 0;
    }

    public void Start()
    {
        var now = DateTime.Now.Ticks;
        nextTick = now + (step - (nextTick % step));
        timer.Interval = GetTimerInterval();
        timer.Start();
    }

    public void Stop()
    {
        timer.Stop();
        nextTick = DateTime.Now.Ticks % step;
    }
}
John Leidegren
  • 59,920
  • 20
  • 131
  • 152
3

By making use of ReactiveExtensions you could use the following code if you were interested in doing something as simple as printing to the console.

using System;
using System.Reactive.Linq;
namespace ConsoleApplicationExample
{
    class Program
    {
        static void Main()
        {
            Observable.Interval(TimeSpan.FromMinutes(1))
            .Subscribe(_ =>
            {                   
                Console.WriteLine(DateTime.Now.ToString());
            });
            Console.WriteLine(DateTime.Now.ToString()); 
            Console.ReadLine();
        }
    }
}
Mdev
  • 453
  • 8
  • 19
2

What about Quartz.NET? I think its a good framework to do timed actions.

Jehof
  • 34,674
  • 10
  • 123
  • 155
  • 1
    The biggest problem with Quartz.NET is the simple lack of a HOWTO with a console app and class instantiation and PEM setting to prove how it works. The library looks really promising but the learning curve is a barrier to adoption. – Snowy Sep 20 '11 at 02:42
1

Running a bit of code to see if the minute has changed once per second should not require much CPU time, and should be acceptable.

Walt W
  • 3,261
  • 3
  • 30
  • 37
  • 1
    Not really, this seems very much like a polling loop to me which should be avoided whenever possible as it can really mess up power usage on laptops. – Chris Chilvers Aug 25 '09 at 19:34
  • While you're right it might reduce battery a bit, I doubt it'd be that noticeable . . . We're talking about code that executes a tiny fraction of a millisecond. But, yeah, aquinas' answer is a more elegant approach. – Walt W Aug 25 '09 at 19:56
  • Remember that he's looking for every minute *on the minute* functionality here, so precision appears to be key. That's why it's hard to avoid a polling loop unless you do aquinas' method (and that yields accuracy sufficient for the application). – Walt W Aug 25 '09 at 19:57
1

You could set up two timers. An initial short interval timer (perhaps to fire every second, but dependent on how presice the second timer must fire on the minute).

You would fire the short interval timer only until the desired start time of the main interval timer is reached. Once the initial time is reached, the second main interval timer can be activated, and the short interval timer can be deactivated.

void StartTimer()
{

  shortIntervalTimer.Interval = 1000;
  mainIntervalTimer.Interval = 60000; 

  shortIntervalTimer.Tick += 
    new System.EventHandler(this.shortIntervalTimer_Tick);
  mainIntervalTimer.Tick += 
    new System.EventHandler(mainIntervalTimer_Tick);

  shortIntervalTimer.Start();

}

private void shortIntervalTimer_Tick(object sender, System.EventArgs e)
{
  if (DateTime.Now.Second == 0)
    {
      mainIntervalTimer.Start();
      shortIntervalTimer.Stop();
    }
}

private void mainIntervalTimer_Tick(object sender, System.EventArgs e)
{
  // do what you need here //
}
Jayden
  • 2,656
  • 2
  • 26
  • 31
1

Alternatively, you could sleep to pause execution until it times out which should be close to your desired time. This will only wake the computer when the sleep finishes so it'll save you CPU time and let the CPU power down between processing events.

This has the advantage of modifying the timeout so that it will not drift.

int timeout = 0;

while (true)  {
  timeout = (60 - DateTime.Now.Seconds) * 1000 - DateTime.Now.Millisecond;
  Thread.Sleep(timeout);

  // do your stuff here
}
Ron Warholic
  • 9,994
  • 31
  • 47
0

Use a timer set to run every second (or millisecond, whatever your accuracy threshold is), and then code the method to run your functionality if and only if the current time is within that threshold past the "on the minute" point.

Charles Bretana
  • 143,358
  • 22
  • 150
  • 216
  • Now this is definitely a polling loop which should be avoided whenever possible as it can really mess up power usage on laptops, especially when the timer could just be set to fire in 60 seconds rather than every second – Chris Chilvers Aug 25 '09 at 19:39
0

What I'm using for scheduled tasks is a System.Threading.Timer(System.Threading.TimerCallback, object, int, int) with the callback set to the code I want to execute based on the interval which is supplied in milliseconds for the period value.

Brian Surowiec
  • 17,123
  • 8
  • 41
  • 64
0

What about a combination of aquinas' answer and 'polling': (apologies for the mixture of languages)

def waitForNearlyAMinute:
    secsNow = DateTime.Now.Second;
    waitFor = 55 - secsNow;
    setupTimer(waitFor, pollForMinuteEdge)

def pollForMinuteEdge:
    if (DateTime.Now.Second == 0):
        print "Hello, World!";
        waitForNearlyAMinute();
    else:
        setupTimer(0.5, pollForMinuteEdge)
Greg
  • 2,163
  • 1
  • 18
  • 40
-1

I have a solution based on Environment.TickCount

    static void Main(string[] args)
    {
        //constatnt total miliseconds to one minute
        const Int32 minuteMilisecond = 60 * 1000;

        //get actual datetime
        DateTime actualDateTime = DateTime.UtcNow;

        //compenzation to one minute
        Int32 nexTimer = Environment.TickCount + ((59 - actualDateTime.Second) * 1000) + (999 - actualDateTime.Millisecond);

        //random fuction to simulate different delays on thread
        Random rnd = new Random();

        //main loop
        while (true) 
        {
            if (Environment.TickCount > nexTimer)
            {
                nexTimer += minuteMilisecond;

                //execute your code here every minute
                Console.WriteLine($"actual DateTime: {DateTime.Now.ToString("yyyy.MM.dd HH:mm:ss:ffff")}");
            }

            //random sleep between 100 - 200 ms
            Thread.Sleep(rnd.Next(100, 200));
        }
    }
RicmanXFF
  • 11
  • 4