c# - 未收集垃圾的线程/线程池线程/C#/.NET

标签 c# .net

在我的 C#/.NET 3.5 程序中,我使用线程池线程(delegate+BeginInvoke/EndInvoke)来并行化和加速一些文件加载​​。 SystemInternals 工具 ProcessExplorer 显示进程中的线程数随着时间的推移而增加,而我希望保持不变。看起来一些线程/线程句柄无缘无故地闲逛。

有趣的是,我找不到线程如何增长的模式,而且似乎偶尔发生,每次启动应用程序时都没有可重复的模式。我花了一些时间进行分析,以下是一些观察结果:

1) 代码如下所示:

ArrayList IAsyncResult_s = new ArrayList();
   AsyncProcessing thread1 = processRasterLayer;
... ArrayList filesToRender....
 foreach (string FileName in filesToRender)
    {
      string fileName2 = FileName;
      GeoImage partialImage1;
      IAsyncResult asyncResult = thread1.BeginInvoke(
                                fileName2, .....,
                                out partialImage1, ..., null, null);
      IAsyncResult_s.Add(asyncResult);
      asyncResult = null;
    }
.................
//block and render all
foreach (IAsyncResult asyncResult in IAsyncResult_s)
{
  GeoImage partialImage1;
  thread1.EndInvoke(
  out partialImage1,   , asyncResult);
  //render image.. some calls to render partial image here
  partialImage1.Dispose();
  partialImage1 = null;
}
IAsyncResult_s.Clear();
IAsyncResult_s = null;
thread1 = null;

2) 进程线程数
我的跟踪显示,在循环内执行期间,ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads);给出像 493, 1000 这样的数字。
在循环结束时 , , ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads);给出数字 500, 1000。因此,可用线程的数量返回到相同SystemInternals ProcessExplorer 报告的进程线程数和 API System.Diagnostics.Process.GetCurrentProcess() .Threads.Count 是循环前的 16 个,循环后的大约 21 个。
如果我再次调用这些循环,进程中的线程数会增长,但不是每次都以固定数字增长,而是每次重复上述代码时增长 1-4,因此增长为 16->21->22->26->31 ...

3)强制垃圾收集没有htelp
我试图通过垃圾回收来摆脱那些额外的线程,但这并没有将它们从进程中删除。

4)分析工具
我正在使用 RedGates 内存和性能分析器,但还没有找到明显的原因。我看到了几个额外的威胁及其对象(ThreadContext 等)挂起,但没有看到将这些线程保存在内存中的对象。我很确定那些额外的线程参与了上面的循环工作,因为我在调用中添加了线程名称,并且它们仍然具有我给它们的名称。

5) 智能追踪
Intelitrace 调试还显示了额外的线程挂起。他们还有我给他们起的名字。但有趣的是,它还显示现在挂起的同一线程过去曾被上述循环使用,而且同一线程正在从我的代码中的计时器执行一些与计时器相关的事件。

6) 定位问题
因此,当我禁用上述异步处理 filse 的循环并按顺序加载文件时,我没有额外的线程,并且我的应用程序中的线程数是恒定的,大约为 16。

7) 关于 SetMaxThreads :这是它在我的机器上的外观(XP,.NET 3.5):

像这样的代码:

ThreadPool.GetAvailableThreads(出AvailableWorkerThreads,出AvailableCompletionPortThreads);
ThreadPool.GetMaxThreads(out MaxWorkerThreads, out MaxCompletionPortThreads);
ThreadPool.GetMinThreads(out MinWorkerThreads, out MinCompletionPortThreads);

给出结果:

MinWorkerThreads:2 MaxWorkerThreads:500 MinCompletionPortThreads:2 MaxCompletionPortThreads:1000 AvailableWorkerThreads:500 AvailableCompletionPortThreads:1000

我的应用程序可能同时使用 8 个工作线程。我认为 SetMaxThreads 没有问题。

8)
从功能上讲,到目前为止,我对上述解决方案没有任何问题。但不知何故,如果工具报告我的应用程序中的线程数量在增长,这看起来像是某种“资源泄漏”,我想解决它。看起来有些线程句柄无缘无故地挂着。

9) 这是一篇有趣的文章。一旦 EndInvoke 被调用,线程资源就会被清理干净。我在我的代码中这样做。文章俏皮话:..”。因为 EndInvoke 在生成的线程之后进行清理,所以您必须确保为每个 BeginInvoke 调用 EndInvoke。” “如果线程池线程已经退出,EndInvoke 会执行以下操作:它清理退出线程的松散末端并处理其资源。”见:http://en.csharp-online.net/Asynchronous_Programming%E2%80%94BeginInvoke_EndInvoke

10)另一篇有趣的文章。作者说他有线程句柄泄漏,因为他从非 gui 线程创建控件。这是一篇非常详尽的文章,参见:http://msmvps.com/blogs/senthil/archive/2008/05/29/the-case-of-the-leaking-thread-handles.aspx

11)另一篇有趣的文章。它谈到了 ThreadPool.SetMinThreads 属性。似乎不是 ThreadPool.SetMaxThreads 而是 ThreadPool.SetMinThreads 可以对 ThradPool 进行有用的控制。这篇文章让我大开眼界,让我思考 ThreadPool 的工作原理以及它可能导致的性能问题。文章是:http_://www.dotnetperls.com/threadpool-setminthreads。另一个类似的是:http_://www.codeproject.com/Articles/3813/NET-s-ThreadPool-Class-Behind-The-Scenes

12)另一篇有趣的文章。它正在谈论 ThreadPool 的节流问题。文章提到每秒增加 2 个新线程的 ThreadPool 限制。参见 http_://社交。 msdn。微软。 com/forums/en-US/clr/thread/3325cb32-371b-4f3e-965f-6ca88538dc3e/

13) 所以,在大约 30 次测试中,我只看到分配的线程数量会减少 2 倍。但是,它确实发生了。我曾经看到线程编号像 16->....->31->61-> ->30->16。所以,它又回到了 16。它不经常发生,也不是等待时间,它就像正在进行的大型事件,然后是一段持续的低水平事件。

14) ThreadPool.SetMinThreads 方法文档。它讨论了线程池每秒 2 个新线程的限制。目前尚不清楚设置此属性是否会消除该限制。 http_://msdn.microsoft。 com/en-ca/图书馆/系统。 threading.threadpool.setminthreads(v=vs.90).aspx

最佳答案

所以答案是:这里没有泄漏。这就是线程池的工作方式。它保留完成工作的线程,因此您下次需要线程时不必支付创建线程的费用。如果您有许多并发工作项,则池中的线程数会增加,但它们会在 MaxWorkerThreads 处达到最大值。 (它与垃圾收集器无关。)

有关更多信息,请参阅这篇文章:
http://msdn.microsoft.com/en-us/library/0ka9477y.aspx

关于c# - 未收集垃圾的线程/线程池线程/C#/.NET,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15459099/

相关文章:

c# - 单个整数的 Web API ResponseType

c# - 使用外部 SQL Server 2008 R2 更新 linq 中的语句

c# - 将成员序列化为 Object 或 List<Object>

c# - 在 C# 应用程序中查找内存泄漏

c# - 如何判断基于套接字的客户端中的连接是否已断开?

c# - ASP.Net MVC 将项目添加到绑定(bind)的下拉列表

c# - Windows 服务的位置 *不* 在我的项目中

c# - 需要有关 ASP.NET MVC 3 体系结构的一些指导

.net - .net 中的高性能文本文件解析

c# - 配置 NServiceBus 以记录导致消息重试的异常