C# iis工作进程内存使用和处理对象之间的关系?

C# iis工作进程内存使用和处理对象之间的关系?,c#,asp.net,entity-framework,iis,C#,Asp.net,Entity Framework,Iis,我们通过下面的代码通过实体框架从SQLServer获取大量数据 using (var db=new Entities()) { var list = db.spGetRecs().ToList(); ///rest of the codes } 记录的数量约为400万条,之后iis变得如此繁重,其内存使用量达到690 mb左右,并且工作进程根本不释放内存。 您可以简单地想象这样一种情况,即多个用户使用如此大的内存,然后当然会发生内存不足异

我们通过下面的代码通过实体框架从SQLServer获取大量数据

    using (var db=new Entities())
    {
        var list = db.spGetRecs().ToList();

       ///rest of the codes
    }
记录的数量约为400万条,之后iis变得如此繁重,其内存使用量达到690 mb左右,并且工作进程根本不释放内存。 您可以简单地想象这样一种情况,即多个用户使用如此大的内存,然后当然会发生
内存不足异常

我将在这里提出三个问题:

1.首先,为什么iis工作进程不释放内存

2.其次,在处理完数据后,如何强制iis工作进程释放内存

3.第三,为什么尽管我处理了所有与这个巨大数据相关的对象,但它对
iis
内存使用没有任何影响?!那么,在与windows进程无关的情况下处理对象有什么意义

我没有写整行代码,因为我不想让问题复杂化,也不想让你分心这个问题背后可能存在的挑战性概念


顺便说一句,在我调用垃圾收集器GC.Collect()之后,它从iis工作进程释放了大约20Mb的内存。

即使导致内存分配的对象已被释放,进程仍然可以报告高内存分配。内存可能仍然被认为是保留的,但没有提交,不过老实说,深入了解垃圾收集如何通过释放死掉的对象来工作并不是我真正需要担心的事情,除非跟踪无法解释的不断增长的内存使用

第一步应该始终最小化内存占用,以适应并发请求。对于大型请求,您应该考虑实现具有后台进程的请求队列,以确保在任意给定时间内处理有限的并发请求的请求,或者使用不同服务器上的资源,以免影响Web服务器的响应性。 最小化内存占用大小提示:

  • 使用包含轻量级实体定义的有界上下文来定义流程所需的最小字段。例如,如果一个实体通常包含50多个列,其中一些字段是您不需要的大字符串、二进制数据等,那么使用一个带有实体定义的有界上下文(该实体定义仅引用您需要的10列)将节省内存
  • 获取合理的数据位。利用分页在任何时候仅检索可管理的数据子集。即一次1000条记录。利用
    Skip
    Take
    以及
    OrderBy
    子句,然后查看返回的记录的数量,以评估是否还有更多的页面需要检索。(而不是依赖一个潜在的昂贵的
    Count
    查询。)
  • 对于繁重的查询/处理,后台进程是一个更好的解决方案。如果它们可以针对只读副本而不是主数据库运行,那就更好了。您的web服务器可以接收一个带有给定参数集的请求,而不是启动昂贵的查询(这会使服务器陷入饥饿),它们只需在处理队列表中创建一个记录,以向后台进程发出信号,让其拾取该记录并对其进行处理。该后台进程可以在完全不同的服务器或服务器场上运行。如果用户正在等待结果,web服务器可以定期轮询后台工作人员的状态更新,并在处理完成后显示处理队列表或相关结果表中的结果。队列可以由多个工作人员提供服务,前提是您有一个同步上下文来协调队列并分配工作,以避免多个工作人员选择同一个工作

    编辑: 当涉及到垃圾收集时,您会感到担心,因为进程(垃圾收集器)正在保持内存的提交状态。您希望,如果所讨论的代码需要分配1GB的ram,则在执行dispose或甚至GC.Collect()之后,内存使用量将减少1GB。没有。内存仍将显示为已提交给进程,但仍可用于该进程的代码。您可以通过运行大型查询并让结果实体/数据过期来测试这一点。例如,我有一个来自另一个SO问题的最新测试数据集,其中填充了300万行数据。在系统负载相当大的情况下,我尝试使用以下方法将所有3M行读取到内存中:

    using (var context = new TestDbContext())
    {
        var test = context.Messages.ToList();
        Assert.IsTrue(true);
    }
    
    根据process monitor的数据,这一过程持续了一段时间,然后出现了大约1.7GB的内存不足异常。所以我把它改成:

    using (var context = new TestDbContext())
    {
        var test = context.Messages.Take(1000000).ToList();
        Assert.IsTrue(true);
    }
    
    这是使用~500MB的RAM完成的。。所以我让它连续执行了4次:

    using (var context = new TestDbContext())
    {
        var test = context.Messages.Take(1000000).ToList();
        Assert.IsTrue(true);
    }
    using (var context = new TestDbContext())
    {
        var test = context.Messages.Take(1000000).ToList();
        Assert.IsTrue(true);
    }
    using (var context = new TestDbContext())
    {
        var test = context.Messages.Take(1000000).ToList();
        Assert.IsTrue(true);
    }
    using (var context = new TestDbContext())
    {
        var test = context.Messages.Take(1000000).ToList();
        Assert.IsTrue(true);
    }
    
    现在,如果最初的1M是“泄漏的”,那么我的应用程序进程就会耗尽内存。但事实并非如此。第一个上升到约500MB,第二个上升到约1.1GB,第三个上升到1.6GB。下一个开始爬升,然后锯齿形下降到~1GB,然后继续爬升回~1.5GB。每一次后续读取都会锯齿状下降,以保持在1.4和1.5GB之间。我重复了6、10遍。有时,它的锯齿波低至200MB,有时几乎什么都没有。交换到磁盘时不会出现内存不足异常或性能下降。调用GC.Collect()不会明显释放内存,即使重复调用或在每次使用
    块后调用。内存被“释放”,并在每次后续调用中重复使用。每次运行或附加读取块的性能是一致的

    当并行运行测试的多个实例时,您可能会看到性能下降,因为每次运行都会分配尽可能多的内存,直到达到极限。所需的组合RAM超过了系统上的可用内存,因此每个RAM轮流将其内存块假脱机到光盘。结果是ju的性能显著下降