我有一个用.NET 4.0编写的Windows窗体应用程序。最近,在执行一些测试时,我注意到句柄存在一些问题。下表显示了结果:
正如您所看到的,只有增加的句柄类型是Event
。
所以我的问题是:所描述的问题是否可能是由Windows窗体应用程序引起的?我的意思是,我没有使用AutoResetEvent
或ManualResetEvent
来同步线程。我确实使用线程,但从上面的表中可以看出线程句柄的数量似乎没问题。那么,我认为CLR管理得好吗?
是否可以由我在我的应用中使用的任何第三方组件引起?
如果不清楚,我会尽力回答你的问题。谢谢你的帮助!
答案 0 :(得分:1)
事件是.Net中内存泄漏的主要来源,AutoResetEvent
和ManualResetEvent
非常命名错误。他们不是原因。
当你看到这样的事情时:
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);
}
}
}
}
}
}