What references do System.Timers.Timer
instance create in my application if I create/subscribe and start it?
Here are references I know about:
- Reference to an instance of our timer that we store in some variable.
- Implicit reference when we subscribe to
Elapsed
event
I assume that there must be some additional references created when we Start
our timer. But what are they?
Here is a snippet of code that I experimented with:
class Program
{
static void Main(string[] args)
{
var timers = new List<Timer>();
Console.WriteLine("Timers to create/start and subscribe: ");
// step 1
var count = 1000000;
for (int i = 0; i < count; i++)
{
var timer = new Timer(100000);
timers.Add(timer);
// step 2
timer.Elapsed += Callback;
timer.Start();
}
// step 3: 7mb -> 240mb
Console.WriteLine("All timers subscribed and started");
Task.Delay(TimeSpan.FromSeconds(15)).Wait();
// step 4
timers.ForEach(t => t.Elapsed -= Callback);
Console.WriteLine("All timers unsubscribed");
// step 5: uncomment next two lines to have timers GC collected.
// timers.ForEach(t => t.Stop());
// Console.WriteLine("All timers stopped");
// step 6
timers.Clear();
Console.WriteLine("Collection cleared");
// step 7
do
{
Console.WriteLine("Press Enter to GC.Collect");
Console.ReadLine();
GC.Collect();
Console.WriteLine("GC Collected");
}
while (true);
}
private static void Callback(object sender, ElapsedEventArgs e)
{
Console.WriteLine("callback");
}
}
Here is what I do here:
- Create one million instances of
Timer
and store them into List - For each timer: subscribe to
Elapsed
event passing to it a static method reference and then callStart
. - Once this is done my application grows in memory from 7mb up to 240mb.
- After this I unsubscribe from all timers
Elapsed
event, hence killing implicit event subscription reference. - OPTIONAL: Stop all timers.
- Clear list of timers to remove references to their instances.
- GC.Collect to release memory allocated for timers.
If Step 5
is executed(timers are Stopped), then on GC.Collect
step I can see that memory occupied by my application reduces from 240mb to about 20mb which means that Timers were recycled correctly.
If Step 5
is omitted, then on GC.Collect
step memory usage remains on 240mb level no matter how long I wait and force GC collection. Which actually means that timer instances remain in memory and can not be collected.
From what I know how GC works, my timers should be cleared from memory if they are not accessible from the root(meaning no references).
Are there any other references to my timers that I can eliminate without explicitly calling timer.Stop()
?
Is there any other way to prevent memory leak while not calling Stop
method?