12

There is a problem with standard System.Timers.Timer behaviour. The timer raise Elapsed event with some interval. But when time of execution inside Elapsed event handler exceed timer interval then thread pool begin queuing event handling. This is a problem in my case. This is because with my Elapsed event handler I fetch some data from database and doing something with it and finally save results back to database. But data handling should be provided only once. So, is there a way to prevent from queuing elapse events for System.Timers.Timer.

As illustration for this issue you can consider next test program:

public class EntryPoint
{

    private static void TimeProc(object state, ElapsedEventArgs e)
    {
        Console.WriteLine("Current time {0} on the thread {1}", DateTime.Now, Thread.CurrentThread.ManagedThreadId);
        Thread.Sleep(20000);
    }

    static void Main(string[] args)
    {
        Console.WriteLine("Press <Enter> for finishing\n\n");
        ThreadPool.SetMaxThreads(10, 10);
        System.Timers.Timer MyTimer = new System.Timers.Timer(1000);
        MyTimer.Elapsed += new ElapsedEventHandler(TimeProc);
        MyTimer.Start();
        Console.ReadLine();
        MyTimer.Stop();
    }
}

And possible output will be as here:

Current time 03.02.2011 0:00:09 on the thread 4
Current time 03.02.2011 0:00:10 on the thread 5
Current time 03.02.2011 0:00:12 on the thread 6
Current time 03.02.2011 0:00:13 on the thread 7
Current time 03.02.2011 0:00:14 on the thread 8
Current time 03.02.2011 0:00:15 on the thread 9
Current time 03.02.2011 0:00:16 on the thread 10
Current time 03.02.2011 0:00:17 on the thread 11
Current time 03.02.2011 0:00:18 on the thread 12
Current time 03.02.2011 0:00:19 on the thread 13
Current time 03.02.2011 0:00:30 on the thread 4
Current time 03.02.2011 0:00:30 on the thread 5

Possible solutions:

1) It was inspired by:C# Timer vs Thread in Service

And has a code like here regarding to mentioned above sample:

    public class EntryPoint
    {
        private static System.Timers.Timer MyTimer;
        private static void TimeProc(object state, ElapsedEventArgs e)
        {
            Console.WriteLine("Current time {0} on the thread {1}", DateTime.Now, Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(20000);
            MyTimer.Enabled = true;
        }

        static void Main(string[] args)
        {
            Console.WriteLine("Press <Enter> for finishing\n\n");
            ThreadPool.SetMaxThreads(10, 10);
            MyTimer = new System.Timers.Timer(1000);
            MyTimer.AutoReset = false;

            MyTimer.Elapsed += new ElapsedEventHandler(TimeProc);
            MyTimer.Enabled = true;
            Console.ReadLine();

        }
    }

2) Second way is about SynchronizingObject, but it is a valuable only for Windows form application or required additional development of code for implementing object that would be implements ISynchronizeInvoke interface. More about this way you can find here

So, for now I will prefer first solution.

Community
  • 1
  • 1
apros
  • 2,848
  • 3
  • 27
  • 31
  • the timer doesn't have a problem... this occurs all over the place. It's actually nice that it queues up Elapsed events. if this were a hardware interrupt you'd blow your interval and you can't get that back. – Scott M. Feb 02 '11 at 15:42
  • If the data should only be handled once then why the repeating timer or do you mean at most once every X time? – Chris Feb 02 '11 at 15:44
  • @Chris In my case another application save some data to a database and my application should read it and handle. – apros Feb 02 '11 at 15:53

5 Answers5

17

What I usually do in this case is stop the timer at the start of the Elapsed handler and start it again at the end. This way, you are only handling one tick at a time.

UPDATE:

Per the MSDN link, I think what they mean is that you can set your own flag (but still have the ticks come in), but thread safety measures should be taken as well.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
Mark Avenius
  • 13,679
  • 6
  • 42
  • 50
  • Yes, you are right. But it seems not a right way, due to the fact that it required additional resource for stoping and starting timer. – apros Feb 02 '11 at 15:48
  • 1
    @apros: The behavior you are experiencing is by design. If you set a timer to tick every thirty seconds for a minute, you would expect two ticks events to be fired, right? If you want it to behave differently, this is the way to do it. – Mark Avenius Feb 02 '11 at 15:50
  • MSDN claim here: http://msdn.microsoft.com/en-us/library/system.timers.timer.elapsed.aspx "One way to resolve this race condition is to set a flag that tells the event handler for the Elapsed event to ignore subsequent events. ". So, I hope, there should exists another way to resolve this issue. – apros Feb 02 '11 at 15:56
  • If you take this approach, you want to restart your timer in a finally block. Otherwise, you may get an exception while processing and the timer will never restart. For this and a few other reasons, I don't think this is a good approach. – Chris Shain Feb 02 '11 at 16:22
  • 3
    This snippet is very flawed, it isn't thread-safe. Interlocked is required. Review MSDN example code in the Timer.Stop() method docs. – Hans Passant Feb 02 '11 at 16:33
  • @Hans: I realize that it is not thread-safe which is why I noted at the end that the point I was trying to make was how to use the flag that MSDN mentioned, but that thread-safety measures should be taken. – Mark Avenius Feb 02 '11 at 18:27
4

I would say simply stop it and then start it after your lengthy execution like this.

tmr.Stop();
//Your lengthy execution code goes here
tmr.Start();
Shekhar_Pro
  • 18,056
  • 9
  • 55
  • 79
  • 1
    It's usally what I do however you should also be aware of this when using stop : http://msdn.microsoft.com/en-us/library/system.timers.timer.stop.aspx the part at the begining in yellow warn the user from a second lunch while stopping. – lollancf37 Feb 02 '11 at 15:45
  • @Chris if you were talking about thread none in his case but me not long a go I worked with a tomer in my servie and I had to be able to capture other events while letting my execution code finish. That's why I started to talk about it. – lollancf37 Feb 02 '11 at 15:50
  • @Shekhar_Pro if you are aware of it you can remove the handler from the delegate before stopping the timer no ? – lollancf37 Feb 02 '11 at 15:51
  • i know.. but never tried.. seems like a good idea... i'll check it out.. Thanx – Shekhar_Pro Feb 02 '11 at 15:55
  • This doesn't work, two Elapsed calls can be pending at the same time. – Hans Passant Feb 02 '11 at 16:35
4

The behavior you are seeing is by design. Either set a SynchronizingObject on the timer, or use another timer (such as System.Threading.Timer) that doesn't tick on multiple threads.

Chris Shain
  • 50,833
  • 6
  • 93
  • 125
  • the information you give regarding System.Threading.Timer is incorrect. see http://msdn.microsoft.com/en-us/library/system.threading.timer.aspx - "The callback method executed by the timer should be reentrant, because it is called on ThreadPool threads. The callback can be executed simultaneously on two thread pool threads if the timer interval is less than the time required to execute the callback, or if all thread pool threads are in use and the callback is queued multiple times." Perhaps you were instead thinking of System.Windows.Forms.Timer – bkr Apr 29 '14 at 19:04
  • additionally, setting a synchronization object (if my understanding is correct) will not prevent queuing, it will just limit it to processing them one at a time out of the queue. see http://msdn.microsoft.com/en-us/magazine/cc164015.aspx for a comparison of timers that goes into some detail on the behavior. – bkr Apr 29 '14 at 19:10
2

Since none of the answers are thread safe, let me propose one that is:

void oneHundredMS_Elapsed(object sender, System.Timers.ElapsedEventArgs e) {

  if (setTimerBodyRunning()) { //only proceed to body if it is not already processing; setTimerBodyRunning must be thread-safe
    // here you do your long running operation
    setTimerBodyFinished();
  }
}

As you can see, the timer handler first checks to see if it is not already running, and only proceeds to the body if false is returned. If true is returned, then the handler quickly returns and ticks do not queue (which they would have had a simple lock statement been used). Here are the definitions for setTimerBodyRunning and setTimerBodyFinished:

private bool setTimerBodyRunning() {
        bool retVal = false;
        lock (timerBodyRunning) { //timerBodyRunning is type object and it holds a bool.  
            //The reason it is object and not bool is so it can be locked on to ensure thread safety
            if (!((bool)timerBodyRunning)) {
                timerBodyRunning = true;
                retVal = true;
            }
        }
        return retVal;
    }

private void setTimerBodyFinished() {
    lock (timerBodyRunning) {
        timerBodyRunning = false;
    }
}

Here's how you'd initialize and start the timer:

object timerBodyRunning = new object();
timerBodyRunning = false;
System.Timers.Timer timerFrequency100MS = new System.Timers.Timer();
timerFrequency100MS.Interval = FREQUENCY_MS; //it will fire every 100 milliseconds
timerFrequency100MS.Elapsed += new System.Timers.ElapsedEventHandler(oneHundredMS_Elapsed);
timerFrequency100MS.Start();
public wireless
  • 767
  • 8
  • 20
  • This takes care of the questions of thread safety. Consider placing the setTimerBodyFinished call in a finally block to make sure it always gets called. – user3112728 Aug 17 '17 at 04:06
2

I just create a static flag variable. This way my timer keeps running, but the code is simply bypassed if it the method has not completed before the next timer cycle.

In the method used for the timer, test if an event is in progress.

Timer_Method_Called()
{
  if (eventInProgress == 0)
  {
     // flag event as in progress
     eventInProcess == 1;

     // perform code....

     // after code is complete, allow the method to execute
     eventInProgress == 0;
  }
}
Jose
  • 37
  • 1
  • 4
    this is not thread safe, and you are using == for what I'm assuming you meant to be your assignments (=). – bkr Apr 29 '14 at 19:12