C# .NET timers and multithreading

By | December 24, 2024

This is an old note about timers in .NET that might be interesting to someone. I believe the same concept could be applied in other programming languages.

As noted by Joe Albahari in his article about timers (http://www.albahari.com/threading/part3.aspx#_Timers), they provide a more effective way to manage memory and resources compared to an approach where we create a thread containing code like this:

volatile static bool keepGoing = true; 

while (keepGoing) 
{ 
    // do something if something bad occurred somewhere in code 
    keepGoing = false; 
}

The same idea is described by Jeffrey Richter in his CLR via C# book (take a look at the chapter “Performing a Periodic Compute-Bound Operation”).

What does this mean? With an infinite loop, you can encounter issues such as resource leaks and other problems. By using timers, once a thread finishes its work, it frees any resources you may have forgotten to release, and a new thread is created to perform the same task again.

A common example that comes to mind is a WPF application with a separate thread that should periodically monitor something or repeat a sequence of actions.

Of course, we should not forget about handling errors and implementing cancellation in our threads, which makes the work more complex.

Let’s return to the topic of timers and take a closer look at them.

There are four types of timers:

  1. General-purpose multithreading timers
  2. Special-purpose single-threaded timers

The first group of timers includes the following classes:

  • System.Threading.Timer
  • System.Timers.Timer

Both of these use the thread pool, and System.Timers.Timer is a wrapper around System.Threading.Timer. Below is a simple console application that demonstrates how System.Threading.Timer works:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Threading; 

namespace ConsoleApplication4 { 
    class Program { 
        static void Main(string[] args) { 
            Timer t = new Timer(testDelegate,null, 0, 250); 
            Console.ReadLine(); 
        } 
        
        static void testDelegate(object obj) { 
            System.Diagnostics.Trace.WriteLine("call to testDelegate, thread id: " 
            + Thread.CurrentThread.ManagedThreadId + " , is tp: " + Thread.CurrentThread.IsThreadPoolThread); 
        } 
    } 
}

The trace output of the execution will be something like the one shown below:

image

As you can see, testDelegate is called on different threads from the thread pool. As a result:

  • We can’t directly change UI elements from WPF or Windows Forms in the timer’s handler using these types of timers (since System.Timers.Timer also uses the thread pool) without employing a synchronization mechanism.
  • We need to ensure that the timer’s handler (delegate) is written in a thread-safe manner because a new event could be triggered before the previous one has finished.

I recommend you take a look at source code of .NET System.Threading.Timerhttps://referencesource.microsoft.com/#mscorlib/system/threading/timer.cs. to get detailed information about how Timer class work. In brief:

  • there is only one queue (class TimerQueue) in the  AppDomain realized as singleton pattern that maintains all active timers (class TimerQueueTimer). Queue is realized as an unordered doubly-linked list due needing of good performance for inserting and deleting operations
  • each time we create new timer it is added to the queue
  • AppDomainTimerCallback is called by VM when the native timer fires.  I think native timer is some kernel object that can be accessed trough Win API and used to provide all necessary functionality to managed code
  • AppDomainTimerCallback  calls FireNextTimers method that walks trough linked list gets timer object (TimerQueueTimer) and checks if is there need to call to Fire method. It gives the first available timer object and fires on this thread (timerToFireOnThisThread.Fire()). Other timers will be added to the thread pool (QueueTimerCompletion(timer))
  • Fire method is just wrapper under CallCallback method which executes our timer’s handler (to which point m_timerCallback).

And here is source code of System.Timers.Timer – https://referencesource.microsoft.com/#System/services/timers/system/timers/Timer.cs

Here is the place in the code where the timer member will be set by the value of the System.Threading.Timer object:

csharpCopy codetimer = new System.Threading.Timer(callback, cookie, i, autoReset ? i : Timeout.Infinite);
System.Timers.Timer provides additional functionality, which is described here: Joe Albahari’s Timers Article and on MSDN. Interestingly, Jeffrey Richter doesn't recommend using this class, as it likely should have been removed.

The second group of timers is designed to simplify working with timers in WPF and Windows Forms applications:

  • System.Windows.Threading.DispatcherTimer (WPF)
  • System.Windows.Forms.Timer (Windows Forms)

These are quite similar to System.Timers.Timer in terms of their members, but instead of using the thread pool, they rely on a message-pumping approach. As a result, the timer’s handler code is executed on the same thread as the UI code. A disadvantage of this approach is the inability to run long-running code in the handler without blocking the UI thread, which can lead to freezing the user interface.

After a brief analysis of timers, let’s return to our goal — replacing a thread with an infinite loop with a timer’s handler that is periodically called by the timer. Below is a simple WPF application with a bug.

public partial class MainWindow : Window { 
    public MainWindow() { 
        InitializeComponent(); 
    } 
    private void Button_Click(object sender, RoutedEventArgs e) { 
        Timer timer = new Timer(testTimerCode, null, 0, 3000); 
    } 
    
    private static void testTimerCode(object obj) { 
        System.Diagnostics.Trace.WriteLine("test timer tick"); 
    } 
 }

As shown below timer’s handler will be called only 2 times

image

The reason is that when the timer variable becomes unreachable, the garbage collector stops the timer.

This code implements it in correct way (we use static variable that holds our timer intead)

public partial class MainWindow : Window { 
    private static Timer timer; public MainWindow() { 
        InitializeComponent(); 
    }

    private void Button_Click(object sender, RoutedEventArgs e) { 
        StartTest.IsEnabled = false; 
        timer = new Timer(testTimerCode, null, 0, 3000); 
    } 
    
    private static void testTimerCode(object obj) { 
        System.Diagnostics.Trace.WriteLine("test timer tick"); 
    } 
}

This timer works until you stop it. Another issue with System.Threading.Timer is that it can be triggered multiple times, which means two or more threads could execute the timer’s callback handler simultaneously. This problem is illustrated in the example below:

public partial class MainWindow : Window { 
    private static Timer timer; public MainWindow() { 
        InitializeComponent(); 
    } 
    
    private void Button_Click(object sender, RoutedEventArgs e) { 
        StartTest.IsEnabled = false; 
        timer = new Timer(testTimerCode, null, 0, 3);
    } 
    
    private static void testTimerCode(object obj) { 
        System.Diagnostics.Trace.WriteLine("test timer tick, " + 
            Thread.CurrentThread.ManagedThreadId); Thread.Sleep(2000); 
    } 
}

A result of execution is the following

image

Because the testTimerCode is now executing for a longer duration, the timer triggers new threads more frequently, causing multiple thread pool threads to execute simultaneously. A great workaround for this issue is described in Jeffry’s book:

public partial class MainWindow : Window { 
    private static Timer timer; 
    
    public MainWindow() { 
        InitializeComponent(); 
        } 
        
        private void Button_Click(object sender, RoutedEventArgs e) { 
            StartTest.IsEnabled = false; 
            timer = new Timer(testTimerCode, null, Timeout.Infinite, Timeout.Infinite); 
            timer.Change(0, Timeout.Infinite); 
        } 
        
        private static void testTimerCode(object obj) { 
            System.Diagnostics.Trace.WriteLine("test timer tick, " + 
            Thread.CurrentThread.ManagedThreadId); 
            
            // simulate time-consuming work 
            Thread.Sleep(2000); 
            timer.Change(3000, Timeout.Infinite); 
        } 
    }

timer.Change(0, Timeout.Infinite); – starts the timer immediately (due time = 0) but ensures it doesn’t repeat (interval = Timeout.Infinite) thus it will be executed at least once. Then in

timer.Change(3000, Timeout.Infinite); – sets the timer to fire again after 3000 milliseconds (3 seconds) and ensures it doesn’t repeat automatically (Timeout.Infinite).

So, if you need to run some task every 5 minuets for example, just change first argument of Change method in testTimerCode handler as shown below:

timer.Change(5 * 60 * 1000, Timeout.Infinite);

Now we see that the current thread finishes, and then a new one starts again.

image

Leave a Reply