处理.NET应用程序中的泄漏(事件类型)

时间:2014-02-16 18:36:46

标签: c# multithreading memory-leaks thread-synchronization handles

我有一个用.NET 4.0编写的Windows窗体应用程序。最近,在执行一些测试时,我注意到句柄存在一些问题。下表显示了结果:

enter image description here

正如您所看到的,只有增加的句柄类型是Event

所以我的问题是:所描述的问题是否可能是由Windows窗体应用程序引起的?我的意思是,我没有使用AutoResetEventManualResetEvent来同步线程。我确实使用线程,但从上面的表中可以看出线程句柄的数量似乎没问题。那么,我认为CLR管理得好吗?

是否可以由我在我的应用中使用的任何第三方组件引起?

如果不清楚,我会尽力回答你的问题。谢谢你的帮助!

2 个答案:

答案 0 :(得分:1)

事件是.Net中内存泄漏的主要来源,AutoResetEventManualResetEvent 非常命名错误。他们不是原因。

当你看到这样的事情时:

myForm.OnClicked += Form_ClickHandler

这是正在讨论的事件类型。注册事件处理程序时,事件源(如OnClicked)会保留对处理程序的引用。如果你创建和注册新的处理程序,你必须取消注册事件(如myForm.OnClicked -= Form_ClickHandler),否则你的内存使用将继续增长。

欲了解更多信息:

答案 1 :(得分:1)

这个答案有点晚了,但是我在调​​查一些代码中的一个非常相似的问题时碰到了这个问题,并在反汇编CreateEvent的syscall处放置了一个断点,从而找到了答案。希望其他人也会发现此答案有用,即使对于您的特定用例来说为时已晚。

答案是存在争用时,.NET会为各种线程原语创建事件内核对象。值得注意的是,我制作了一个测试应用程序,可以显示它们是在使用“ lock”语句时创建的,尽管大概任何Slim线程原语都将执行类似的延迟创建。

重要的是要注意,句柄没有泄漏,尽管数量增加可能表明代码其他地方存在泄漏。当垃圾收集器收集创建它们的对象(例如,lock语句中提供的对象)时,这些句柄将被释放。

我在下面粘贴了我的测试代码,它将以小规模展示泄漏(我的测试机上大约有100个泄漏的事件句柄-您的行驶里程可能会有所不同)。

一些特定的兴趣点:

  • 清除列表并运行GC.Collect()后,将清除所有创建的句柄。

  • 将ThreadCount设置为1将阻止创建任何事件句柄。

  • 类似地,注释掉lock语句将不会创建任何句柄。

  • ThreadCount的计算中删除index(第72行)将大大减少争用,从而阻止创建几乎所有的句柄。

  • 无论您让它运行多长时间,它都永远不会创建200个以上的句柄(由于某种原因,.NET似乎每个对象创建2个句柄)。

using System.Collections.Generic;
using System.Threading;

namespace Dummy.Net
{
    public static class Program
    {
        private static readonly int ObjectCount = 100;
        private static readonly int ThreadCount = System.Environment.ProcessorCount - 1;

        private static readonly List<object> _objects = new List<object>(ObjectCount);
        private static readonly List<Thread> _threads = new List<Thread>(ThreadCount);

        private static int _currentIndex = 0;
        private static volatile bool _finished = false;
        private static readonly ManualResetEventSlim _ready = new ManualResetEventSlim(false, 1024);

        public static void Main(string[] args)
        {
            for (int i = 0; i < ObjectCount; ++i)
            {
                _objects.Add(new object());
            }

            for (int i = 0; i < ThreadCount; ++i)
            {
                var thread = new Thread(ThreadMain);
                thread.Name = $"Thread {i}";
                thread.Start();
                _threads.Add(thread);
            }

            System.Console.WriteLine("Ready.");

            Thread.Sleep(10000);

            _ready.Set();
            System.Console.WriteLine("Started.");

            Thread.Sleep(10000);

            _finished = true;

            foreach (var thread in _threads)
            {
                thread.Join();
            }

            System.Console.WriteLine("Finished.");

            Thread.Sleep(3000);

            System.Console.WriteLine("Collecting.");

            _objects.Clear();
            System.GC.Collect();

            Thread.Sleep(3000);

            System.Console.WriteLine("Collected.");

            Thread.Sleep(3000);
        }

        private static void ThreadMain()
        {
            _ready.Wait();

            while (!_finished)
            {
                int rawIndex = Interlocked.Increment(ref _currentIndex);
                int index = (rawIndex / ThreadCount) % ObjectCount;
                bool sleep = rawIndex % ThreadCount == 0;

                if (!sleep)
                {
                    Thread.Sleep(10);
                }

                object obj = _objects[index];
                lock (obj)
                {
                    if (sleep)
                    {
                        Thread.Sleep(250);
                    }
                }
            }
        }
    } 
}