7

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

在此处输入图像描述

如您所见,唯一增加的句柄类型是Event.

所以我的问题是:所描述的问题是否可能是由 Windows 窗体应用程序引起的?我的意思是,我不使用AutoResetEventor同步线程ManualResetEvent。我确实使用线程,但是从上面的表格中可以看出线程句柄的数量似乎还可以。那么,我假设它们由 CLR 很好地管理?

它可能是由我也在我的应用程序中使用的任何第三方组件引起的吗?

如果不清楚,我将尝试回答您的问题。感谢帮助!

4

2 回答 2

2

事件是.Net 中内存泄漏的主要来源,而且AutoResetEvent命名非常糟糕ManualResetEvent。他们不是原因。

当你看到这样的事情时:

myForm.OnClicked += Form_ClickHandler

这就是我们所说的事件类型。当您注册事件处理程序时,事件源(如OnClicked)会保留对处理程序的引用。如果您创建和注册新的处理程序,您必须取消注册该事件(如myForm.OnClicked -= Form_ClickHandler),否则您的内存使用将不断增长。

欲了解更多信息:

于 2014-02-16T21:08:15.013 回答
1

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

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

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

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

几个具体的兴趣点:

  • 一旦列表被清除并GC.Collect()运行,任何创建的句柄都将被清除。

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

  • 同样,注释掉该lock语句将导致不创建句柄。

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

  • 无论您让它运行多长时间,它都不会创建超过 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);
                    }
                }
            }
        }
    } 
}
于 2020-04-24T22:48:49.557 回答