在.NET中优雅地处理内存不足异常(或完全避免它)

时间:2012-07-04 16:36:26

标签: .net out-of-memory

我有一个工作处理器需要并行处理约300个作业(作业可能需要5分钟才能完成,但它们通常网络限制)。

我遇到的问题是工作往往是特定类型的团块。为简单起见,我们假设有六种工作类型,JobAJobF

JobA - JobE是受网络约束的,可以很高兴地让300个人在一起运行而根本没有对系统征税(事实上,我已经设法让并排运行超过1,500个测试)。 JobF(一种新的作业类型)也是网络绑定的,但它需要相当大的内存并实际使用GDI功能。

我确保用using小心地处理所有GDI对象,根据探查器,我没有泄漏任何东西。简单地说,并行运行300 JobF会占用比.NET更愿意给我的内存。

处理此问题的最佳做法是什么?我的第一个想法是确定我有多少内存开销,并在接近限制时限制产生新的工作(至少JobF个工作)。我无法实现这一点,因为我无法找到任何方法可靠地确定框架愿意在内存方面分配我的内容。我还必须猜测作业使用的最大内存,这看起来有点不稳定。

我的下一个计划是,如果我获得OOM并重新安排失败的工作,那就简单地限制。不幸的是,OOM​​可以在任何地方发生,而不仅仅是在有问题的工作中。实际上,最常见的地方是管理作业的主要工作线程。事实上,这会导致进程正常关闭(如果可能),重新启动并尝试恢复。虽然这种方法有效,但它的时间和资源都是令人讨厌和浪费的 - 远比仅仅回收那个特定的工作更糟糕。

是否有一种标准的方法来处理这种情况(添加更多内存是一个选项并且将完成,但应用程序应该正确处理这种情况,而不仅仅是炸弹)?

4 个答案:

答案 0 :(得分:2)

  

只是并行运行300 JobF会占用比.Net愿意给我更多的内存。

那么,就是不要这样做。在系统ThreadPool中排队。或者,或者,横向扩展并将负载分配给更多系统。

另外,如果发生内存不足异常,请查看CERs以至少运行清理代码。

更新:另外要注意的是,因为你提到你使用GDI,它可以为are not out of memory conditions的内容抛出OutOfMemoryException

答案 1 :(得分:2)

我正在做一些类似于你的情况的东西,我选择了一种方法,我有一个任务处理器(在一个节点上运行的主队列管理器)和在一个或多个节点上运行的AGENTS。

每个代理都作为单独的进程运行。它们:

  • 检查任务可用性
  • 下载所需数据
  • 流程数据
  • 上传结果

队列管理器的设计方式是,如果任何代理在执行作业期间失败,则会在一段时间后将其重新委托给另一个代理。

8 agents running side-by-side in one box

BTW,考虑不要同时并行运行所有任务,因为切换上下文确实存在一些开销(可能很大)。在您的情况下,您可能会使用不必要的PROTOCOL流量使网络饱和,而不是真正的数据流量。

这个设计的另一个优点是,如果我开始落后于数据处理,我总是可以打开一台机器(比如说亚马逊C2实例)并运行多个代理,这将有助于更快地完成任务库

回答你的问题:

每个主机都会尽可能多地使用,因为在一台主机上运行的代理数量有限。当一项任务完成后,另一项任务将无限期地进行。我不使用数据库。任务不是时间关键的,所以我有一个过程可以在传入的数据集上四处转动,并在以前的运行中出现故障时创建新的任务。具体地:

http://access3.streamsink.com/archive/(来源数据)

http://access3.streamsink.com/tbstrips/(计算结果)

在每个队列管理器运行时,扫描源和目标,减去结果集,将文件名转换为任务。

还有一些:

我正在使用Web服务来获取作业信息/返回结果,使用简单的http来获取要处理的数据。

最后:

这比我所拥有的2个经理/代理人对更简单 - 其他人在某种程度上更加复杂,所以我不会在这里详细介绍它。使用电子邮件:)

答案 2 :(得分:1)

理想情况下可以分区为流程配置文件。 CPU绑定,内存绑定,IO绑定,网络绑定。我是并行处理的新手,但是TPL做得好的是CPU限制,并且不能真正调整MaxDegreeOfParallelism。

开始是CPU绑定获取MaxDegreeOfParallelism = System.Environment.ProcessorCount -1

其他所有东西都得到MaxDegreeOfParallelism = 100.我知道你说网络的东西会扩大,但在某些时候,限制是你的带宽。旋转300个工作(吃内存)确实给你更多的吞吐量?如果是这样的话,请看Joradao的答案。

答案 3 :(得分:0)

如果您的对象实现了IDisposable接口,则不应该依赖Garbage Recollector,因为这可能会导致内存泄漏。

例如,如果您有该课程:

class Mamerto : IDisposable
{
    public void methodA()
    {
        // do something
    }

    public void methodB()
    {
        // do something
    }

    public void Dispose()
    {
        // release resources
    }

你以这种方式使用那个类:

using( var m = new Mamerto() )
{
    m.methodA();
    m.methodB();
    // you should call dispose here!
}

垃圾回收器会将m对象标记为“准备删除”,将其置于Gen 0集合中。当Garbage recollector尝试删除Gen 0上的所有对象时,检测Dispose方法并自动将对象提升为Gen 1(因为删除该对象并不“那么容易”)。第1代对象不像Gen 0对象那样经常被检查,然后可能导致内存泄漏。

请阅读该文章以获取更多信息http://msdn.microsoft.com/en-us/magazine/bb985011.aspx

如果你继续进行明确的Dispose,那么你可以避免这种恼人的泄漏。

using( var m = new Mamerto() )
{
    m.methodA();
    m.methodB();
    m.Dispose();
}