8

.Net 4. ThreadLocal<> 实现 IDisposable。但似乎调用 Dispose() 实际上并没有释放对所持有的线程本地对象的引用。

此代码重现了该问题:

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<ThreadLocalData>();
            ThreadLocal<ThreadLocalData> threadLocal = new ThreadLocal<ThreadLocalData>(() =>
            {
                var ret = new ThreadLocalData();
                lock (threadLocalInstances)
                    threadLocalInstances.Add(ret);
                return ret;
            });
            // Do some multithreaded stuff
            int sum = Enumerable.Range(0, 100).AsParallel().Select(
                i => 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<> 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();
        }
    }
}

线程本地数据存储对大对象的引用。如果没有手动清空引用,GC 不会收集这些大对象。我使用任务管理器来观察内存消耗。我还运行内存分析器。我在收集垃圾后拍了一张快照。分析器显示泄漏的对象是 GCHandle 的根,并在此处分配:

mscorlib!System.Threading.ThreadLocal<T>.GenericHolder<U,V,W>.get_Boxed()
mscorlib!System.Threading.ThreadLocal<T>.get_Value()
ConsoleApplication2!ConsoleApplication2.Program.<>c__DisplayClass3.<Main>b__2( int ) Program.cs

这似乎是 ThreadLocal<> 设计中的一个缺陷。存储所有分配的对象以进行进一步清理的技巧是丑陋的。关于如何解决这个问题的任何想法?

4

2 回答 2

1

R在 .Net 4.5 DP 上运行,我看不出在您的应用程序中按下或不按下有任何区别。如果 4.0 中确实存在内存泄漏,那么它似乎已修复。

(4.5是就地更新,所以我不能在同一台电脑上测试4.0,抱歉。)

于 2011-11-20T15:32:49.950 回答
1

内存可能已经被垃圾回收了,但 CLR 进程还没有放弃它。它倾向于保留分配的内存一段时间,以防以后需要它,因此它不必进行昂贵的内存分配。

于 2011-09-27T09:46:16.067 回答