I need to create a bunch of timer as local variable to do something like:
void Foo()
{
Timer t = new Timer(myTimerCallback,null,1000,Timeout.Infinite);
}
It's worth pointing out that the keeping reference issue is specific to System.Threading.Timer
and not System.Timers.Timer
.
This is not a solution to your problem but I think it's still relevant to the topic.
System.Threading.Timer
's Timer(TimerCallback callback)
constructor (the one that doesn't take the dueTime
and others) uses this
for state
which means that the timer will keep a reference to itself and this means that it will survive garbage collection.
public Timer(TimerCallback callback)
{
(...)
TimerSetup(callback, this, (UInt32)dueTime, (UInt32)period, ref stackMark);
}
Timer 'C' is created using Timer(TimerCallback callback)
and it just keeps going after GC.Collect()
.
A
B
C
A
B
C
GC Collected
B
C
C
B
B
C
class TimerExperiment
{
System.Threading.Timer timerB;
public TimerExperiment()
{
StartTimer("A"); // Not keeping this timer
timerB = StartTimer("B"); // Keeping this timer
StartTimer2("C"); // Not keeping this timer
}
static System.Threading.Timer StartTimer(string name) {
return new System.Threading.Timer(_ =>
{
Console.WriteLine($"{name}");
}, null, dueTime: Delay(name), period: TimeSpan.FromSeconds(1));
}
static System.Threading.Timer StartTimer2(string name)
{
//Create the timer using the constructor which only takes the callback
var t = new System.Threading.Timer( _ => Console.WriteLine($"{name}"));
t.Change(dueTime: Delay(name), period: TimeSpan.FromSeconds(1));
return t;
}
static TimeSpan Delay(string name)
=> TimeSpan.FromMilliseconds(Convert.ToInt64(name[0])*10);
}
class Program
{
static async Task Main(string[] args)
{
var withTimers = new TimerExperiment();
await Task.Delay(TimeSpan.FromSeconds(2));
GC.Collect();
Console.WriteLine("GC Collected");
Console.ReadLine();
}
}