【问题标题】:System.Drawing memory leak within Parallel.ForEach(...)Parallel.ForEach(...) 中的 System.Drawing 内存泄漏
【发布时间】:2012-06-19 06:33:30
【问题描述】:

我正在使用 System.Drawing 类处理图像列表。如果我在正常的 foreach 循环中处理图像,一切都很好,但是当我使用 Parallel.ForEach 遍历图像(图像我不是指任何一次性 System.Drawing.Image 实例,而是图像的源(url))时,我运行记不清。我的进程变得越来越大(> 1GB)。图形缓冲区似乎没有被释放,尽管我很注意释放所有资源。这可以通过切换到非并行 foreach(...) 来确认。该进程保持在大约 60 MB。

你遇到过这样的问题吗?

        // memory "leak". process grows beyound 1GB to infinity
        Parallel.ForEach(urls, url =>
        {
            ImageResizer.DownloadAndResizeImage(url);
        });


        // no memory "leak"
        foreach (string url in urls)
        {
            ImageResizer.DownloadAndResizeImage(url);
        }

【问题讨论】:

  • 好吧,既然您已经向我们展示了no代码,您希望我们如何帮助您?
  • 彻底阅读我的问题?我认为所有信息都在那里,但我会在我的问题中添加一些抽象代码,尽管我怀疑它是否会有所帮助。
  • 大约有 110 亿种不同的方式来编写代码以顺序和并行方式与图像交互。其中有些会泄漏内存,有些会出现泄漏内存,有些则绝对不会。我们不知道你写了什么。尝试创建一个 shortcomplete 示例来演示 问题,并将该代码添加到您的问题中。通常,在尝试构建此类示例的过程中,您会发现指向问题的新线索。
  • 嗯...你好?问题是不是从 Parallel.Invoke 变成了 Parallel.ForEach??
  • Yes chagned form Parallel.Invoke to ForEach...我的错。对不起。

标签: c# memory-leaks task-parallel-library system.drawing


【解决方案1】:

我希望 Parallel.Invoke 在处理时消耗更多内存,因为它试图一次处理超过 1 个图像。 Parallel.Invoke 还将等待所有任务在它返回之前完成,并且因为您说发生“内存不足”错误,我猜它永远不会返回,因此无法知道是否发生内存泄漏。

尝试使用 Parallel.Invoke 仅处理两个图像,看看在处理完成后您的内存是否会回到已知的起点。如果是这样,那么就没有内存泄漏 - 您只是在尝试处理超出系统一次可以处理的内容。

如果是这种情况,请尝试使用 Parallel.ForEach 来限制使用 MaxDegreeOfParallelism 的线程数。

我会尝试的第一件事(假设你有 4 个核心):

Parallel.ForEach( 
    urls, 
    new ParallelOptions { MaxDegreeOfParallelism = 4 }, 
    url => { ImageResizer.DownloadAndResizeImage(url); } 
); 

编辑:

好吧,问题似乎已经从 Parallel.Invoke 更改为 Parallel.ForEach 并添加了一些代码:),但这不应该改变我的答案,因为 Parallel.ForEach 也会等到所有任务都完成。

我猜想方法 DownloadAndResizeImage() 可能是罪魁祸首,而不是并行。为了正确地异步处理请求,使用低级网络 API 需要进行大量开发。使用 Microsoft 的 WebClient 或 HttpWebRequest 对象具有已知的瓶颈,这些瓶颈限制了可以通过多线程异步发出的请求数量。我知道这一点,因为我最近尝试了同样的事情并最终自己编写了套接字层。嬉皮士!

因此,可能发生的情况是,每个请求都已发出,但一次只处理两个请求,而其他请求将堆积在队列中等待轮到他们。但是在他们等待的时候,所有的对象都在初始化,导致内存增长。最终(如果您有足够的内存),您会开始看到由于执行时间过长的失败请求而发生了一些超时。

有一些免费软件应用程序旨在筛选数百个请求。我建议获取他们的一些低级代码并在您的应用程序中实现它。这是开始学习套接字的好地方。

http://msdn.microsoft.com/en-us/magazine/cc300760.aspx

【讨论】:

  • 嗯,这听起来很合理,但使用 ANTS 内存分析器我可以看到实际托管内存保持在非常低的水平并且不会增长。问题是分配的非托管内存很可能由于我使用的位图/图形/GDI+ 东西而疯狂增长。此外,我可以在日志中看到并行发出的 Web 请求并不多。似乎没有 50 多个网络请求处于待处理状态。
  • 我的假设是,如果您在多个线程中使用 GDI+,可能会有一些 GDI+ 错误不会释放图形缓冲区,因为我读到 GDI+ .NET 包装器确实有很多错误是也得到了微软的确认......
  • @Max - 我也考虑了图像处理程序,但不需要代​​码来确认,所以我正在尝试与 Parallel 相关的真实情况并使用 HttpWebRequest .您的理论很有道理,您可以尝试提出一个新问题并发布您的 GDI+ 代码的来源。
  • @Max - 这里还有一些值得深思的地方...social.msdn.microsoft.com/Forums/en/parallelextensions/thread/…
【解决方案2】:

Parallel.ForEach 启动的线程数往往多于 CPU 内核数。它可能显示内存泄漏,因为有 50 个线程或正在运行。

在处理过程中需要暂停调试器,看看是否有太多线程在运行。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-07-06
    • 1970-01-01
    • 2015-12-01
    • 2010-12-20
    • 2020-05-26
    • 2012-08-20
    • 2017-10-14
    相关资源
    最近更新 更多