Очень высокое использование памяти in.NET 4.0


У меня есть служба C# Windows, которую я недавно переместил из .NET 3.5 в .NET 4.0. Никаких других изменений в коде сделано не было.

при работе на 3.5 использование памяти для данной рабочей нагрузки составляло примерно 1,5 ГБ памяти, а пропускная способность-20 X в секунду. (X не имеет значения в контексте этого вопроса.)

точно такая же служба, работающая на 4.0, использует от 3 ГБ до 5 ГБ+ памяти и получает менее 4 X в секунду. На самом деле, служба, как правило, в конечном итоге остановка по мере использования памяти продолжает расти, пока моя система не будет находиться на уровне 99% использования, а обмен файлами подкачки не сойдет с ума.

Я не уверен, что это связано со сбором мусора, или что, но у меня возникли проблемы с выяснением этого. Моя служба окон использует GC "Server" через коммутатор файлов конфигурации, показанный ниже:

  <runtime>
    <gcServer enabled="true"/>
  </runtime>

изменение этого параметра на false, похоже, не имело значения. Кроме того, из чтения, которое я сделал на новом GC в 4.0, большие изменения только влияние режима АРМ ГК, не режиме сервера ГК. Так что, возможно, GC не имеет ничего общего с этим вопросом.

идеи?

5 60

5 ответов:

Ну это было интересно.

основной причиной оказывается изменение в поведении класса LocalReport служб SQL Server Reporting Services (v2010) при запуске этого поверх .NET 4.0.

в основном, Microsoft изменила поведение обработки RDLC, чтобы каждый раз, когда отчет обрабатывался, это делалось в отдельном домене приложения. Это было сделано специально для устранения утечки памяти, вызванной невозможностью выгрузки сборок из приложения домены. Когда класс LocalReport обрабатывает RDLC-файл, он фактически создает сборку на лету и загружает ее в домен приложения.

в моем случае, из-за большого объема отчета, который я обрабатывал, это приводило к очень большому количеству системы.Во время выполнения.Удаленное взаимодействие.Создаются объекты ServerIdentity. Это был мой совет к причине, так как я был смущен относительно того, почему обработка RLDC требовала удаленного доступа.

конечно, вызвать метод класса в другом приложении домен, удаленное взаимодействие-это именно то, что вы используете. В .NET 3.5 это не было необходимо, так как по умолчанию rdlc-сборка была загружена в тот же домен приложения. Однако в .NET 4.0 по умолчанию создается новый домен приложения.

исправление было довольно легко. Сначала мне нужно было включить устаревшую политику безопасности, используя следующую конфигурацию:

  <runtime>
    <NetFx40_LegacySecurityPolicy enabled="true"/>
  </runtime>

затем мне нужно было заставить RDLCs обрабатываться в том же домене приложения, что и моя служба, вызвав следующее:

myLocalReport.ExecuteReportInCurrentAppDomain(AppDomain.CurrentDomain.Evidence);

это решило проблему.

я столкнулся с этим вопросом. И это правда, что домены приложений создаются, а не очищаются. Однако я бы не рекомендовал возвращаться к наследию. Они могут быть очищены с помощью ReleaseSandboxAppDomain ().

LocalReport report = new LocalReport();
...
report.ReleaseSandboxAppDomain();

некоторые другие вещи, которые я также делаю, чтобы очистить:

Отменить подписку на любые события вложенной обработки отчетов, Очистить Источники Данных, Утилизируйте отчет.

наша служба Windows обрабатывает несколько сообщений в секунду, и нет никаких утечек.

вы хотите

возможно, какой-то API изменил семантику или даже может быть ошибка в версии 4.0 фреймворка

просто для полноты картины, если кто-то ищет эквивалент ASP.Net web.config настройки, это:

  <system.web>
    <trust legacyCasModel="true" level="Full"/>
  </system.web>

ExecuteReportInCurrentAppDomain работает то же самое.

благодаря этому социальная ссылка MSDN.

похоже, что Microsoft попыталась поместить отчет в свое собственное отдельное пространство памяти, чтобы обойти все утечки памяти, а не исправить их. При этом они ввели некоторые жесткие сбои и в конечном итоге имели больше утечек памяти в любом случае. Они, кажется, кэшируют определение отчета, но никогда не используют его и никогда не очищают его, и каждый новый отчет создает новое определение отчета, занимая все больше и больше памяти.

Я играл вокруг с делать то же самое: использовать отдельный домен приложения и маршал доклад к ней. Я думаю, что это ужасное решение и делает беспорядок очень быстро.

то, что я сделал вместо этого, похоже: разделите отчетную часть вашей программы на свою собственную отдельную программу отчетов. Это, оказывается, хороший способ организовать свой код в любом случае.

хитрая часть передает информацию в отдельную программу. Используйте Process класс для запуска нового экземпляра программы отчетов и передачи любых параметров это нужно в командной строке. Первым параметром должно быть перечисление или аналогичное значение, указывающее отчет, который должен быть напечатан. Мой код для этого в основной программе выглядит примерно так:

const string sReportsProgram = "SomethingReports.exe";

public static void RunReport1(DateTime pDate, int pSomeID, int pSomeOtherID) {
   RunWithArgs(ReportType.Report1, pDate, pSomeID, pSomeOtherID);
}

public static void RunReport2(int pSomeID) {
   RunWithArgs(ReportType.Report2, pSomeID);
}

// TODO: currently no support for quoted args
static void RunWithArgs(params object[] pArgs) {
   // .Join here is my own extension method which calls string.Join
   RunWithArgs(pArgs.Select(arg => arg.ToString()).Join(" "));
}

static void RunWithArgs(string pArgs) {
   Console.WriteLine("Running Report Program: {0} {1}", sReportsProgram, pArgs);
   var process = new Process();
   process.StartInfo.FileName = sReportsProgram;
   process.StartInfo.Arguments = pArgs;
   process.Start();
}

и программа выглядит примерно так:

[STAThread]
static void Main(string[] pArgs) {
   Application.EnableVisualStyles();
   Application.SetCompatibleTextRenderingDefault(false);

   var reportType = (ReportType)Enum.Parse(typeof(ReportType), pArgs[0]);
   using (var reportForm = GetReportForm(reportType, pArgs))
      Application.Run(reportForm);
}

static Form GetReportForm(ReportType pReportType, string[] pArgs) {
   switch (pReportType) {
      case ReportType.Report1: return GetReport1Form(pArgs);
      case ReportType.Report2: return GetReport2Form(pArgs);
      default: throw new ArgumentOutOfRangeException("pReportType", pReportType, null);
   }
}

код GetReportForm методы должны извлекать определение отчета, использовать соответствующие аргументы для получения набора данных, передавать данные и любые другие аргументы в отчет, а затем помещать отчет в отчет просмотр формы и возврат ссылки на форму. Обратите внимание, что можно извлечь большую часть этого процесса, чтобы вы могли в основном сказать: "Дайте мне форму для этого отчета из этой сборки, используя эти данные и эти аргументы".

также обратите внимание, что обе программы должны иметь возможность видеть ваши типы данных, которые имеют отношение к этому проекту, поэтому, надеюсь, вы извлекли свои классы данных в свою собственную библиотеку, на которую обе эти программы могут ссылаться. Это не будет работать имейте все классы данных в основной программе, потому что у вас будет циклическая зависимость между основной программой и программой отчета.

не переусердствуйте с аргументами, либо. Сделайте любой запрос к базе данных, который вам нужен в программе отчетов; не передавайте огромный список объектов (который, вероятно, не будет работать в любом случае). Вы должны просто передавать простые вещи, такие как поля идентификатора базы данных, диапазоны дат и т. д. Если у вас есть особенно сложные параметры, вам может потребоваться нажать эту часть из пользовательского интерфейса в программу отчетов тоже и не передавать их в качестве аргументов в командной строке.

вы также можете поместить ссылку на программу отчетов в вашей основной программе, и в результате .exe и любые связанные с ним .библиотеки DLL будут скопированы в ту же папку вывода. Затем вы можете запустить его без указания пути и просто использовать исполняемый файл сам по себе (т. е.: "SomethingReports.исполняемый.)" Вы также можете удалить библиотеки DLL отчетов из основной программы.

одна проблема с этим заключается в том, что вы получите явную ошибку, если вы никогда не публиковали программу отчетов. Просто фиктивно опубликовать его один раз, чтобы создать манифест, а затем он будет работать.

как только у вас есть эта работа, очень приятно видеть, что память вашей обычной программы остается постоянной при печати отчета. Программа отчетов появляется, занимая больше памяти, чем ваша основная программа, а затем исчезает, полностью очищая ее с помощью вашей основной программы, занимающей не больше памяти, чем она уже имел.

другая проблема может заключаться в том, что каждый экземпляр отчета теперь будет занимать больше памяти, чем раньше, так как теперь они являются целыми отдельными программами. Если пользователь печатает много отчетов и так и не закрывает их, он будет использовать много памяти очень быстро. Но я думаю, что это все еще намного лучше, так как эту память можно легко восстановить, просто закрыв отчеты.

Это также делает ваши отчеты независимыми от вашей основной программы. Они могут оставаться открытыми даже после закрытия главного программы, и вы можете создавать их из командной строки вручную, или из других источников.