This is garbage collection at work, correctly doing its job mind you.
The problem is related to your timers.
Before reading further, note that your trick to use a locally scoped variable was entirely correct. This was just not the problem you were having. So continue doing that, if you hadn't done that you would have had other/more problems.
Ok, so, you construct two timers, since you have two values in your dictionary, and they run "forever", that is until they're collected.
You're not keeping the references to the timers, so eventually the garbage collector will collect one or both of them.
When it does, it'll run the finalizer of the timer before it collects it, and this will stop its execution.
The solution is to hold on to your timers, or to not use timers at all, but let's stay with the timers, the solution is easy to fix:
int i = 0;
var timers = new List<System.Threading.Timer>();
foreach (KeyValuePair<string, object> record in Dict)
{
var _recordStored = record; //THIS IS(was) SUPPOSED TO FIX MY WOES!!!
timers.Add(new System.Threading.Timer(_ => {
... rest of your timer code here
}, null, TimeSpan.Zero, new TimeSpan(0, 0,0,0,1))); // extra end parenthesis here
}
for (; ; )
{
System.Threading.Thread.Sleep(10000);
}
GC.KeepAlive(timers); // prevent the list from being collected which would ...
// end up making the timers eligible for collection (again)
Now: Here's a snafu that will trip you up. If you're running this in the debugger, or in a DEBUG build, the fact that it's the first value that disappears is completely normal, at the same time that it is completely normal for the second value to never disappear.
Why? Because all variables have had their lifetime extended to the duration of the method. In the loop, when you say simply new System.Threading.Timer(...)
, the compiler silently redefines this as this:
var temp = `new System.Threading.Timer(...)`
This variable (temp
) is overwritten on each loop iteration, in effect keeping the last value assigned to it, and this timer will never be collected as long as you are running this in the debugger or in a DEBUG build, since the method that defined it never finishes (the endless loop at the bottom).
The first timer, however, is free to be collected since there is no longer anything keeping a reference to it.
You can verify this with this LINQPad program:
void Main()
{
for (int index = 1; index <= 5; index++)
{
new NoisyObject(index.ToString());
}
for (;;)
{
// do something that will provoke the garbage collector
AllocateSomeMemory();
}
}
private static void AllocateSomeMemory()
{
GC.KeepAlive(new byte[1024]);
}
public class NoisyObject
{
private readonly string _Name;
public NoisyObject(string name)
{
_Name = name;
Debug.WriteLine(name + " constructed");
}
~NoisyObject()
{
Debug.WriteLine(_Name + " finalized");
}
}
When you run this in LINQPad, make sure the little button down to the right in the window is switched to "/o-":

You'll get this output:
1 constructed
2 constructed
3 constructed
4 constructed
5 constructed
4 finalized
3 finalized
2 finalized
1 finalized
Notice how the 5 is never finalized?
Now turn on optimizations by clicking that "/o-" button and turn it to "/o+" to enable optimizations (RELEASE build):
1 constructed
2 constructed
3 constructed
4 constructed
5 constructed
5 finalized
4 finalized
3 finalized
2 finalized
1 finalized
And now 5 is also finalized.
Note: What I said about about a temporary variable being introduced doesn't really happen just like that, but the effect is the same.
If you change the main method of the above LINQPad program to this:
void Main()
{
new NoisyObject("Not finalized when /o-");
for (;;)
{
// do something that will provoke the garbage collector
AllocateSomeMemory();
}
}
you can verify that running it under "/o-" will never finalized the object (well, never is such a strong word, not in the time I waited for it anyway), and it will under /"o+".
Bonus question: If you had dropped the GC.KeepAlive(timer);
from the solution I presented above, wouldn't the fact that I have stored it in a variable change the behavior?
Not really. The problem is the lifetime of the variables. The compiler and JITter uses information about where a variable is used to figure out which variables are considered "live" at any point in time.
For instance this code:
void Main()
{
var obj = new SomeObject();
CallSomeMethodThatNeverReturns();
}
wouldn't this keep obj
alive indefinitely? No. At the point where the method is called, that variable is no longer used, no code can/will access it, and thus the garbage collector is free to collect it.
This is what I meant about the lifetime of a variable. In a DEBUG build (optimizations off), or when running under the debugger, the above code looks like this:
void Main()
{
var obj = new SomeObject();
CallSomeMethodThatNeverReturns();
GC.KeepAlive(obj);
}
in essence preventing the object from being collected. GC.KeepAlive
is essentially a no-op method that the compiler and JITter cannot "optimize away" and will thus seem to use the object in question, thereby extending its lifetime.
This is not done in a RELEASE build (optimizations on), and so a production program might behave differently from under development.
Conclusion: Do your testing on the same type of build that your customers will get.