RDLC内存泄漏

4
在我的应用程序(.NET Framework 4.5)中,我正在渲染一些RDLC报告(50-60个),以便将它们导出为单个PDF文件。
不幸的是,存在一个很大的内存泄漏问题,基本上每个LocalReport都没有被处理。
以下是我的代码:
public void ProcessReport(ReportDataSource[] reportDS, string reportPath)
{
    const string format = "PDF";
    string deviceInfo = null;
    string encoding = String.Empty;
    string mimeType = String.Empty;
    string extension = String.Empty;
    Warning[] warnings = null;
    string[] streamIDs = null;
    Byte[] pdfArray = null;

    using (var report = new LocalReport())
    {
        report.EnableExternalImages = true;
        report.ReportEmbeddedResource = reportPath;
        report.Refresh();

        foreach (var rds in reportDS)
        {
            report.DataSources.Add(rds);
        }
        report.Refresh();

        try
        {
            pdfArray = report.Render(format, deviceInfo, out mimeType, out encoding,
                out extension, out streamIDs, out warnings);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.InnerException.Message);
            throw;
        }

        report.ReleaseSandboxAppDomain();
        report.Dispose();

        //Add pdfArray to MemoryStream and then to PDF - Doesn't leak
    }
}

我通过查看Visual Studio的内存面板发现了内存泄漏。每次调用report.Render时,它会增加20-30MB的内存,直到我关闭应用程序前,这些内存都不会被释放。我确定使用MemoryStream不是问题,因为即使注释掉该行,内存中仍然有200MB-250MB没有释放。这很糟糕,因为运行此应用程序3-4次后,内存就会达到>1GB,甚至无法再运行。我还尝试手动调用GarbageCollector,但并没有起作用。该应用程序是32位的。

我该怎么办来解决这个问题?


你有内存泄漏的证据吗?你在测量什么,看到了什么?你是以32位还是64位进程运行的?你正在进行的进程的性质是什么(是长时间运行的服务器应用程序还是短暂的批处理应用程序)? - Flydog57
1
好奇:为什么在Using中调用对象的Dispose方法?此外,问题的实际症状是什么?我理解为“它不起作用,这就是原因”。请说明您看到的情况和任何错误消息。 - UnhandledExcepSean
另外,你说内存流部分不是问题。你是如何确定的呢? - UnhandledExcepSean
添加了更多信息,主要问题是如果用户调用此函数3次以上,应用程序将达到>1GB的内存,然后崩溃。这只是一些报告批量渲染,一旦它们被渲染并且用户导出或打印它们,我就不再需要它们在内存中了,不幸的是我的代码没有处理它们。 - SilentRage47
你尝试过这里发布的解决方案了吗?https://www.codeproject.com/Questions/636950/How-to-fix-memory-leak-in-Microsoft-Report-rdlc - UnhandledExcepSean
是的,我尝试过启用NetFx40_LegacySecurityPolicy,但我需要使用dynamic,而它无法使用。 - SilentRage47
1个回答

8

我有一个真正的解决方案,并且可以解释为什么!

原来,LocalReport在这里使用.NET Remoting动态创建子应用程序域并运行报告,以避免内部泄漏。然后我们注意到,最终,报告将在10至20分钟后释放所有内存。对于需要生成大量PDF的人来说,这是行不通的。然而,关键在于他们正在使用.NET Remoting。 Remoting的关键部分之一是称为“租赁”的东西。租赁意味着它会保留该Marshal对象一段时间,因为设置Remoting通常很昂贵,而且可能会被多次使用。 LocalReport RDLC正在滥用此功能。

默认情况下,租赁时间为... 10分钟!此外,如果某些内容对其进行了多个调用,则会将另外2分钟添加到等待时间中!因此,它可能在10到20分钟之间随机变化,这取决于调用如何排列。幸运的是,您可以更改此超时的持续时间。不幸的是,您只能在每个应用程序域中设置一次...因此,如果您需要PDF生成以外的远程处理,您可能需要运行另一个服务来运行它,以便您可以更改默认值。要做到这一点,您只需要在启动时运行以下4行代码:

    LifetimeServices.LeaseTime = TimeSpan.FromSeconds(5);
    LifetimeServices.LeaseManagerPollTime = TimeSpan.FromSeconds(5);
    LifetimeServices.RenewOnCallTime = TimeSpan.FromSeconds(1);
    LifetimeServices.SponsorshipTimeout = TimeSpan.FromSeconds(5);

您会看到内存使用情况开始上升,然后在几秒钟内,您应该可以看到内存开始回落。我用内存分析器花了好几天时间才真正追踪到这个问题并意识到发生了什么。

无法在using语句中包装ReportViewer(Dispose崩溃),但是如果直接使用LocalReport,则应该能够包装。之后释放后,如果您想要确保尽力释放该内存,可以调用GC.Collect()。

希望这可以帮助您!

编辑

显然,在生成PDF报告后,您应该调用GC.Collect(0),否则由于某种原因,内存使用情况可能仍然很高。


你介意分享一下在哪里可以添加这段代码吗? 我正在使用 ASP .net 4.5。 - Kevin Ng
应用程序的启动可以在Application_Start或程序Main中进行,但它只能运行一次。 - Daniel Lorenz
关于Dispose()崩溃的问题,StackOverflow上有一个回答提供了一种解决方案(对我很有效):创建一个虚拟的HttpContext来避免这个问题... 参见 https://dev59.com/5qXja4cB1Zd3GeqPMBqC - mBardos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接