我们的一个非托管Windows服务偶尔会在.NET中调用一个方法,该方法会打开一个数据库连接。从第一次调用该方法开始,该服务会随着时间(小时)消耗越来越多的线程句柄,直到无法启动其他应用程序。创建的线程不会继续运行,但是没有释放相应的句柄。
这是几个小时后(闲置)流程在ProcExplorer中的样子,线程列表不再适合屏幕了:
使用.NET Memory Profiler,很明显该进程累积了许多.NET Thread
对象,这些对象无法完成。这解释了线程如何不再运行,但仍然使用句柄。
下一个问题是所有线程的来源,也可以使用此工具回答:
因此,某些Timer
的执行使用CurrentThread
属性创建了一个新线程(遗憾的是,无法通过.NET参考源访问此方法的实现细节)。这解释了为什么线程是连续创建的(尽管我想知道为什么这个属性必须在每次调用时创建一个新线程)。
接下来的问题当然是计时器的来源。幸运的是,只有一个,像这样创建:
这解释了为什么第一次调用.NET函数会启动问题。
根据这些信息,我们创建了以下方法作为解决问题的解决方法,并在服务启动时调用:
public static void StartGC()
{
Thread thread = new Thread(new ThreadStart(delegate
{
while (true)
{
Thread.Sleep(30000);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
}));
thread.IsBackground = true;
thread.Start();
}
显然,GC没有尽快运行,或者根本看不到它。这是为什么?我可以想象GC认为清理Thread对象(消耗很少的内存)是不值得的,但在某些时候它应该清理它们。
另外,CurrentThread
真的应该创建新线程吗?有没有办法防止这种情况发生?如果没有,是否有比我们现有的更漂亮的解决方法?