Writing finalizers is generally tricky. In the msdn documentation for finalizers the following limitations are mentioned.
- The exact time when the finalizer executes during garbage collection is undefined
- The finalizers of two objects are not guaranteed to run in any specific order
- The thread on which the finalizer is run is unspecified.
- Finalizers might not be run at all
#3 has interesting consequences. If a native resources is allocated by a managed ctor (or any other method) and the finalizers is used to de-allocate that then the allocation and de-allocation will not happen on the same thread. The reason being that the CLR uses one (maybe more than one) special thread to run all finalizers on. This is easily verified by the fact that if you query for the thread id inside the finalizers you will get a different id than the main thread the application is being run on.
The thread safety is generally easy to handle but might lead to some tricky and hard to locate problems. Consider the following code
class MyClass
{
public MyClass()
{
tls = 42;
Console.WriteLine("Ctor threadid = {0} TLS={1}", AppDomain.GetCurrentThreadId(), tls);
}
~MyClass()
{
Console.WriteLine("Finalizer threadid = {0} TLS={1}", AppDomain.GetCurrentThreadId(), tls);
}
public void DoWork()
{
Console.WriteLine("DoWork threadid = {0} TLS={1}", AppDomain.GetCurrentThreadId(), tls);
}
[ThreadStatic]
static int tls;
}
class Program
{
static void Main(string[] args)
{
MyClass mc = new MyClass();
mc.DoWork();
mc = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
Here we create a class, use it and finalize it. In all of these 3 methods we print the thread id and also use a special variable named tls.
If you see the tls is marked with the attribute ThreadStatic. The definition of ThreadStatic is “Indicates that the value of a static field is unique for each thread”.
I hope by now you have figured out the gotcha :). Under the hood the CLR uses a native OS concept called Thread Local Usage (TLS) to ensure that the value of tls is unique per thread. TLS uses special per thread data-structure to store that data. Now we have set the value in the ctor and used it in finalizer. Since they run on different threads each will get different values of the same field.
On my system the out put is as follows
Ctor threadid = 5904 TLS=42
DoWork threadid = 5904 TLS=42
Finalizer threadid = 4220 TLS=0
Press any key to continue . . .
As is evident the finalizer ran on a different thread (id is different) and the TLS value is also different from what was set.
So the moral of this story is “Be careful about thread safety of finalizers and do not use thread local storage in it”