【问题标题】:Preventing OutOfMemoryException with GC.Collect()使用 GC.Collect() 防止 OutOfMemoryException
【发布时间】:2014-09-03 12:07:09
【问题描述】:

我写了一个软件来处理图像并减少处理时间,我尝试使用多线程。下面是相关的sn-p。

bool Multithread = CheckMultithread();
UpdateParameters();

if (Multithread)
{
    Parallel.For(0, FileNames.Length ,i => Solve(FileNames[i]));
}
else
{
     foreach (string s in FileNames)
     {
         Solve(s);
     }
}

这是我第一次尝试用 C# 编写多线程代码;但我相信不存在线程问题,因为处理一张图像不会干扰另一张图像的处理。

问题是:如果Multithreadtrue,当处理 200 幅图像时,我会得到一个OutOfMemoryException...我想这种并行实现消耗的内存是顺序等效的 N 倍,其中N 是线程数。

我在单个类中使用非托管代码,但每次使用此类时,它都在 using 上下文中。作为参考,这个类是System.Drawing.Bitmap 的包装器。

每个线程消耗 +/- 400 MB 的内存,当抛出 OutOfMemoryException 时,程序正在使用大约 1300 MB 的内存。即使我有超过 9 GB 的可用内存。

我在Solve 方法中编写了以下解决方法代码。除了这个例外,我在 Solve() 的请求中添加了以下代码

if (GC.GetTotalMemory(false) > 1000*1000*1000)
{
    lock (Manager.dasLock)
    {
        Manager.sw.Start();
        GC.Collect();
        Manager.sw.Stop();
    }
}

通过解决方法,该软件能够处理所有 2000 多张图像而不会耗尽内存,但我的同行抱怨我不应该碰 GC。 那么,如何在不手动调用 GC 的情况下解决此问题?

【问题讨论】:

  • 您是否尝试过保存处理后的图像并在保存后手动处理它们?并且 1,3gb RAM 上限是因为 32 位;)
  • 您认为 Parallel.For 为每次迭代创建一个线程的假设是不正确的。它将集合划分为几个组,每个组在一个线程中运行。
  • 只是为了确定,因为我发现您的最后几段令人困惑:如果您调用 GC.Collect 它会导致 OutOfMemoryException 但没有它即使使用 Parallel.For 循环也一切正常?
  • 这是 32 位应用程序吗?如果是这样,无论您有多少 RAM,您都只有 2 GB 地址空间。将其设为 64 位进程,您的地址空间会增长很多。
  • 可能相关的相关问题:Parallel.ForEach can cause a “Out Of Memory” exception if working with a enumerable with a large object(但不一定是重复的)。如果您设置最大并行度或创建一个采用较小块的自定义实践者,问题会消失吗?

标签: c# garbage-collection out-of-memory task-parallel-library


【解决方案1】:

限制线程数解决了这个问题:

if (Multithread)
{
    ParallelOptions pOptions = new ParallelOptions();
    pOptions.MaxDegreeOfParallelism = Environment.ProcessorCount;
    Parallel.For(0, FileNames.Length, pOptions, i => Solve(FileNames[i]));
}
else
{
    foreach (string s in FileNames)
    {
        Solve(s);
    }
}

【讨论】:

  • Environment.ProcessorCount 无论如何都是MaxDegreeOfParallelism 选项的默认值,所以我怀疑这是一个解决方案。
  • @TheodorZoulias 你确定吗? docs.microsoft.com/en-us/dotnet/api/…
  • 没有了。似乎我将MaxDegreeOfParallelism 与 PLINQ 的WithDegreeOfParallelism 混淆了。 PLINQ 使用固定数量的线程来执行查询;默认情况下,它使用机器中的逻辑核心数。相反,Parallel.ForEach 可以使用可变数量的线程,这取决于 ThreadPool 对注入和停用线程的支持,以最好地适应当前的工作负载。 Source 第 88 页。
【解决方案2】:

虽然没有说明,但如果您正在处理图像,那么您处理的某些对象很有可能实现 IDisposable 接口 - 例如 File 或 Image 对象。

对于所有这些对象,您应该尝试使用“使用”块 (See here)。

很可能,当您调用 GC.Collect 时,它正在处理这些对象,这就是为什么根据我的理解,当您调用 GC.Collect 时,您不会收到异常

【讨论】:

  • 我知道。但正如我所说,我正在为这样的每一个对象调用 Dispose。
  • 你说你在 Solve 结束时调用它。您是否尝试在此处设置断点以确认您到达最后一行?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-06-08
  • 1970-01-01
  • 2018-03-27
  • 1970-01-01
  • 2011-08-06
  • 2023-04-07
  • 1970-01-01
相关资源
最近更新 更多