如何修复作为服务运行的应用程序中连接池引起的线程句柄泄漏?

时间:2016-07-25 11:28:38

标签: c# .net garbage-collection

问题

我们的一个非托管Windows服务偶尔会在.NET中调用一个方法,该方法会打开一个数据库连接。从第一次调用该方法开始,该服务会随着时间(小时)消耗越来越多的线程句柄,直到无法启动其他应用程序。创建的线程不会继续运行,但是没有释放相应的句柄。

这是几个小时后(闲置)流程在ProcExplorer中的样子,线程列表不再适合屏幕了:

ProcExplorer view

系统信息

  • 与.NET 4.0和4.6相同的问题
  • 到目前为止测试的操作系统:Win7,Server 2008R2,Server 2003 R2
  • 数据库系统:MS SQL和MS Access

分析

使用.NET Memory Profiler,很明显该进程累积了许多.NET Thread对象,这些对象无法完成。这解释了线程如何不再运行,但仍然使用句柄。

下一个问题是所有线程的来源,也可以使用此工具回答:

Thread Creation in MemProfiler

因此,某些Timer的执行使用CurrentThread属性创建了一个新线程(遗憾的是,无法通过.NET参考源访问此方法的实现细节)。这解释了为什么线程是连续创建的(尽管我想知道为什么这个属性必须在每次调用时创建一个新线程)。

接下来的问题当然是计时器的来源。幸运的是,只有一个,像这样创建:

Timer creation

这解释了为什么第一次调用.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真的应该创建新线程吗?有没有办法防止这种情况发生?如果没有,是否有比我们现有的更漂亮的解决方法?

0 个答案:

没有答案