Note that there are some explanatory texts on larger screens.

plurals
  1. POThreadLocal<> and memory leak
    text
    copied!<p>.Net 4. ThreadLocal&lt;> implements IDisposable. But it seems that calling Dispose() doesn't actually release references to thread local objects being held.</p> <p>This code reproduces the problem:</p> <pre><code>using System; using System.Collections.Generic; using System.Collections.Concurrent; using System.Linq; using System.Threading; namespace ConsoleApplication2 { class Program { class ThreadLocalData { // Allocate object in LOH public int[] data = new int[10 * 1024 * 1024]; }; static void Main(string[] args) { // Stores references to all thread local object that have been created var threadLocalInstances = new List&lt;ThreadLocalData&gt;(); ThreadLocal&lt;ThreadLocalData&gt; threadLocal = new ThreadLocal&lt;ThreadLocalData&gt;(() =&gt; { var ret = new ThreadLocalData(); lock (threadLocalInstances) threadLocalInstances.Add(ret); return ret; }); // Do some multithreaded stuff int sum = Enumerable.Range(0, 100).AsParallel().Select( i =&gt; threadLocal.Value.data.Sum() + i).Sum(); Console.WriteLine("Sum: {0}", sum); Console.WriteLine("Thread local instances: {0}", threadLocalInstances.Count); // Do our best to release ThreadLocal&lt;&gt; object threadLocal.Dispose(); threadLocal = null; Console.Write("Press R to release memory blocks manually or another key to proceed: "); if (char.ToUpper(Console.ReadKey().KeyChar) == 'R') { foreach (var i in threadLocalInstances) i.data = null; } // Make sure we don't keep the references to LOH objects threadLocalInstances = null; Console.WriteLine(); // Collect the garbage GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); Console.WriteLine("Garbage collected. Open Task Manager to see memory consumption."); Console.Write("Press any key to exit."); Console.ReadKey(); } } } </code></pre> <p>Thread local data stores a reference to a large object. GC doesn't collect these large objects if references are not nulled manually. I used Task Manager to observe memory consumption. I also run memory profiler. I made a snapshot after garbage was collected. The profiler showed that leaked object is rooted by GCHandle and was allocated with here:</p> <pre><code>mscorlib!System.Threading.ThreadLocal&lt;T&gt;.GenericHolder&lt;U,V,W&gt;.get_Boxed() mscorlib!System.Threading.ThreadLocal&lt;T&gt;.get_Value() ConsoleApplication2!ConsoleApplication2.Program.&lt;&gt;c__DisplayClass3.&lt;Main&gt;b__2( int ) Program.cs </code></pre> <p>That seems to be a flaw in ThreadLocal&lt;> design. The trick with storing all allocated objects for further cleanup is ugly. Any ideas on how to work around that?</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload