When TimerCallback is static, it will execute only once and just hang and will even block any other timers that are running such as timer2. It does reach the end of the method and no exceptions are being thrown. If I make the callback non-static then it works as expected calling the method every x seconds. If I remove the database context then it works fine static or not. The System.Threading.Timer should be queueing calls on a worker thread so even IF the db blocks a thread I don’t see how it can block ALL the threads, even the ones that are not accessing the database. Can anyone explain why it is behaving like this?
public class TimerClass
{
private System.Threading.Timer timer1;
private System.Threading.Timer timer2;
public void Start(TimeSpan CheckInterval)
{
//timer2 = new Timer(o => Console.WriteLine("Timer2: " + DateTime.Now.ToString()), null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
timer1 = new Timer(TimerCallback, null, TimeSpan.Zero, CheckInterval);
}
private static void TimerCallback(object state)
{
Console.WriteLine("Timer1: " + DateTime.Now.ToString());
using (MyDbEntities db = new MyDbEntities())
{
foreach (var item in db.Table)
Console.WriteLine(item.Name);
}
Console.WriteLine("Leaving Callback...");
}
}
This is where the Timer is started. After it is started this Main thread will continue fine but the Timers still hang.
static void Main()
{
TimerClass myTimer = new TimerClass();
myTimer.Start(TimeSpan.FromSeconds(1));
Console.WriteLine("Timer started...");
Console.ReadLine(); // pausing here, timers are still in-scope and should be firing
}
No, that’s not how the garbage collector works. myTimer can be collected right after the Start() call since there are no further references to the object. If you want to keep myTimer alive until the end of the method then you’ll have to add GC.KeepAlive(myTimer) at the end the method.
This is a specific problem with timers, particularly System.Threading.Timer. The garbage collector must see a live reference to an object to prevent it from getting garbage collected. No such reference exists in your code, the local variable doesn’t live long enough and the callback is static.
The CLR does make some effort to keep a System.Timers.Timer going, it won’t be collected as long as it is Enabled. But as soon as it is disabled then it is eligible for collection. Which is fine of course, if there is no reference then you couldn’t enable it anymore. A System.Threading.Timer doesn’t have the same mechanism, you must keep it referenced yourself to keep it alive. Which is one of the core reasons that there are two Timer classes that seemingly do the same job. They don’t, System.Timers.Timer has cooties of its own.