Утечка памяти в RDLC - PullRequest
       6

Утечка памяти в RDLC

0 голосов
/ 09 ноября 2018

В моем приложении (.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 get вызывается, добавляются 20-30 МБ, и они никогда не уменьшаются, пока я не закрою приложение. Я уверен, что использование MemoryStream не является проблемой, потому что даже если я прокомментирую, я все еще получаю 200 МБ-250 МБ в памяти, которая никогда не освобождается Это плохо, потому что после запуска этого приложения, например 3-4 раза, оно достигает> 1 ГБ, пока оно даже не запускается Я также пытался вручную вызвать GarbageCollector, но не сработал. Приложение является 32-разрядным.

Что я могу сделать, чтобы это исправить?

1 Ответ

0 голосов
/ 19 апреля 2019

У меня есть реальное решение, и я могу объяснить, почему!

Оказывается, что LocalReport здесь использует .NET Remoting для динамического создания дочернего домена приложения и запуска отчета, чтобы избежать внутренней утечки. Затем мы замечаем, что, в конце концов, отчет освободит всю память через 10-20 минут. Для людей с большим количеством создаваемых PDF-файлов это не сработает. Однако ключевой момент здесь заключается в том, что они используют .NET Remoting. Одна из ключевых частей Remoting - это то, что называется «Лизинг». Лизинг означает, что он будет держать этот объект Marshal некоторое время, поскольку удаленное взаимодействие, как правило, дорого в установке и, вероятно, будет использоваться более одного раза. 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);

Вы увидите, что использование памяти начнет расти, а затем через несколько секунд вы увидите, что память снова начинает работать. Мне потребовались дни с профилировщиком памяти, чтобы действительно отследить это и понять, что происходит.

Вы не можете заключить ReportViewer в оператор using (Dispose crash), но у вас должно получиться это, если вы используете LocalReport напрямую. После этого вы можете вызвать GC.Collect (), если хотите быть вдвойне уверены, что делаете все возможное, чтобы освободить эту память.

Надеюсь, это поможет!

Редактировать

По-видимому, вы должны вызывать GC.Collect (0) после генерации отчета в формате PDF, иначе может показаться, что использование памяти по какой-то причине все еще может быть высоким.

...