2

在我的 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 ProcessExplorerAPI System.Diagnostics.Process.GetCurrentProcess().Threads.Count 是循环前的 16 个,循环后的大约 21 个。如果我再次调用这些循环,则进程中的线程数会增加,但不是每次都增加固定的数量,而是每次重复上述代码时都会增加 1-4,因此会增长为 16->21->22->26->31 ...

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

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

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

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

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

像这样的代码:

ThreadPool.GetAvailableThreads(out AvailableWorkerThreads, out 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 的节流问题。文章提到 ThreadPool 限制每秒增加 2 个新线程。参见 http_://social。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

4

2 回答 2

3

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

有关详细信息,请参阅本文:http: //msdn.microsoft.com/en-us/library/0ka9477y.aspx

于 2013-03-17T09:36:37.530 回答
0

我会考虑消费者生产者模式。线程池背后的想法是回收线程,而不是创建数百个新线程。在最好的情况下,每个 cpu 都有一个线程,并将工作排队。这肯定会更快,因为您避免了无用的上下文切换并等待创建新线程,据我所知,净线程池等待大约一秒钟直到创建新线程,以便让其他线程有机会被回收。

于 2013-03-22T06:32:30.780 回答