109

Does a System.Timers.Timer elapse on a separate thread than the thread that created it?

Lets say I have a class with a timer that fires every 5 seconds. When the timer fires, in the elapsed method, some object is modified. Lets say it takes a long time to modify this object, like 10 seconds. Is it possible that I will run into thread collisions in this scenario?

user113164
  • 1,437
  • 5
  • 17
  • 17
  • This could lead to problems. Note that, in general, threadpool threads are not designed for long-running processes. – Greg D Sep 16 '09 at 22:58
  • 1
    I was running into this, testing with a windows service. What worked for me was disabling the timer as the first instruction in the OnTimer event, performing my tasks, then enabling the timer at the end. This has worked reliably in a production environment for some time. – Steve Feb 20 '20 at 19:28
  • I am surprised Monitor.TryEnter isn't mentioned as an approach here. Seems neater to me than most other approaches, such as stopping the timer. if (Monitor.TryEnter(someObkect) { ..do timer workload..} finally {monitor.exit()} You can also use the else clause to log that the timed event was skipped, maybe add logic to update the timer frequency too. – Frank Sep 16 '21 at 15:25

5 Answers5

211

It depends. The System.Timers.Timer has two modes of operation.

If SynchronizingObject is set to an ISynchronizeInvoke instance then the Elapsed event will execute on the thread hosting the synchronizing object. Usually these ISynchronizeInvoke instances are none other than plain old Control and Form instances that we are all familiar with. So in that case the Elapsed event is invoked on the UI thread and it behaves similar to the System.Windows.Forms.Timer. Otherwise, it really depends on the specific ISynchronizeInvoke instance that was used.

If SynchronizingObject is null then the Elapsed event is invoked on a ThreadPool thread and it behaves similar to the System.Threading.Timer. In fact, it actually uses a System.Threading.Timer behind the scenes and does the marshaling operation after it receives the timer callback if needed.

Brian Gideon
  • 47,849
  • 13
  • 107
  • 150
  • 6
    If you wanted the timer callback to execute on a new thread, should you use a `System.Threading.Timer` or `System.Timers.Timer`? – CJ7 Oct 07 '12 at 00:09
  • 1
    @cj7: Either one can do that. – Brian Gideon Oct 08 '12 at 19:12
  • and if I have a list of complex type (person) and want to have a time inside each person? I need this running on the same thread (all persons), because if it calls the first person method, the second person must wait until the first ends the elapsed event. Can I do that? – Leandro De Mello Fagundes Jan 31 '14 at 14:30
  • 10
    Everyone...`System.Timers.Timer` has two modes of operation. It can run on a randomly assigned thread pool thread OR it can run on whatever thread is hosting the `ISynchronizeInvoke` instance. I don't know how to make that any more clear. `System.Threading.Timer` has little (if anything) to do with the original question. – Brian Gideon Nov 20 '14 at 19:46
  • @LeandroDeMelloFagundes Can't you use `lock` for that? – Ozkan Jun 18 '18 at 06:56
  • Since the SynchronizingObject property's default value is null, your Elapsed event will happen on another thread unless you have set the SynchronizingObject property. – Girl Spider May 20 '21 at 17:35
  • @BrianGideon Will a thread pool timer ALWAYS be a different thread than the one which is invoking the timer? WITHOUT an ISynchronizeInvoke object, is it somehow possible that the same thread will be used for the timer's callback? – CodeMonkey Feb 16 '22 at 10:02
68

For System.Timers.Timer:

See Brian Gideon's answer below

For System.Threading.Timer:

MSDN Documentation on Timers states:

The System.Threading.Timer class makes callbacks on a ThreadPool thread and does not use the event model at all.

So indeed the timer elapses on a different thread.

Community
  • 1
  • 1
Joren
  • 14,472
  • 3
  • 50
  • 54
  • 21
    True, but that is a completely different class. The OP asked about the System.Timers.Timer class. – Brian Gideon Sep 17 '09 at 01:38
  • 2
    Oh, you're right. http://msdn.microsoft.com/en-us/library/system.timers.timer.aspx says "The Elapsed event is raised on a ThreadPool thread." Same conclusion from there I suppose. – Joren Sep 17 '09 at 11:55
  • 7
    Well, yeah, but it is not quite that simple. See my answer. – Brian Gideon Sep 17 '09 at 12:36
25

Each elapsed event will fire in the same thread unless a previous Elapsed is still running.

So it handles the collision for you

try putting this in a console

static void Main(string[] args)
{
    Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
    var timer = new Timer(1000);
    timer.Elapsed += timer_Elapsed;
    timer.Start();
    Console.ReadLine();
}

static void timer_Elapsed(object sender, ElapsedEventArgs e)
{
    Thread.Sleep(2000);
    Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
}

you will get something like this

10
6
12
6
12

where 10 is the calling thread and 6 and 12 are firing from the bg elapsed event. If you remove the Thread.Sleep(2000); you will get something like this

10
6
6
6
6

Since there are no collisions.

But this still leaves u with a problem. if u are firing the event every 5 seconds and it takes 10 seconds to edit u need some locking to skip some edits.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Simon
  • 33,714
  • 21
  • 133
  • 202
  • 9
    Adding a `timer.Stop()` at the beginning of the Elapsed event method and then a `timer.Start()` at the end of the Elapsed event method will keep the Elapsed event from colliding. – Metro Smurf Nov 30 '11 at 19:41
  • 4
    You don't need to put timer.Stop() you just need to define timer.AutoReset = false; then you make timer.Start() after you process the event. I think this is a better way you avoid collisions. – João Antunes Jul 19 '17 at 17:26
  • if Monitor.TryEnter – Frank Sep 16 '21 at 15:07
17

For System.Timers.Timer, on separate thread, if SynchronizingObject is not set.

    static System.Timers.Timer DummyTimer = null;

    static void Main(string[] args)
    {
        try
        {

            Console.WriteLine("Main Thread Id: " + System.Threading.Thread.CurrentThread.ManagedThreadId);

            DummyTimer = new System.Timers.Timer(1000 * 5); // 5 sec interval
            DummyTimer.Enabled = true;
            DummyTimer.Elapsed += new System.Timers.ElapsedEventHandler(OnDummyTimerFired);
            DummyTimer.AutoReset = true;

            DummyTimer.Start();

            Console.WriteLine("Hit any key to exit");
            Console.ReadLine();
        }
        catch (Exception Ex)
        {
            Console.WriteLine(Ex.Message);
        }

        return;
    }

    static void OnDummyTimerFired(object Sender, System.Timers.ElapsedEventArgs e)
    {
        Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);
        return;
    }

Output you'd see if DummyTimer fired on 5 seconds interval:

Main Thread Id: 9
   12
   12
   12
   12
   12
   ... 

So, as seen, OnDummyTimerFired is executed on Workers thread.

No, further complication - If you reduce interval to say 10 ms,

Main Thread Id: 9
   11
   13
   12
   22
   17
   ... 

This is because if prev execution of OnDummyTimerFired isn't done when next tick is fired, then .NET would create a new thread to do this job.

Complicating things further, "The System.Timers.Timer class provides an easy way to deal with this dilemma—it exposes a public SynchronizingObject property. Setting this property to an instance of a Windows Form (or a control on a Windows Form) will ensure that the code in your Elapsed event handler runs on the same thread on which the SynchronizingObject was instantiated."

http://msdn.microsoft.com/en-us/magazine/cc164015.aspx#S2

Ryan_S
  • 304
  • 3
  • 10
Swab.Jat
  • 1,210
  • 1
  • 14
  • 19
15

If the elapsed event takes longer then the interval, it will create another thread to raise the elapsed event. But there is a workaround for this

static void timer_Elapsed(object sender, ElapsedEventArgs e)    
{     
   try
   {
      timer.Stop(); 
      Thread.Sleep(2000);        
      Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);    
   }
   finally
   {
     timer.Start();
   }
}
sth
  • 222,467
  • 53
  • 283
  • 367
Rajan
  • 151
  • 2
  • +1 Simple and clean answer, but this will not work always. Rather one should use lock or SynchronizingObject property of timer. – RollerCosta May 23 '17 at 10:13
  • You have an at least theoretical race condition here, that happens when the timer elapses again before the `Stop`is hit. Unlikely for bigger time periods though, but still not clean. Use a timer with `Autoreset` set to `false` and then the timer is disabled automatically when the event hits. You don't need to call `Stop` then. – Holger Böhnke Jul 01 '21 at 14:18