Bardzo duże zużycie pamięci in.NET 4, 0

Mam usługę C # Windows, którą niedawno przeniosłem z. NET 3.5 do. NET 4.0. Nie wprowadzono żadnych innych zmian w kodzie.

Podczas pracy na 3.5, wykorzystanie pamięci dla danego obciążenia roboczego wynosiło około 1.5 GB pamięci, a przepustowość wynosiła 20 X na sekundę. (X nie ma znaczenia w kontekście tego pytania.)

Dokładnie ta sama usługa działająca na 4.0 zużywa od 3 GB do 5 GB + pamięci i pobiera mniej niż 4 X na sekundę. W rzeczywistości usługa zazwyczaj kończy się opóźnieniem jako pamięć wykorzystanie nadal rośnie, dopóki mój system nie będzie działał w 99% wykorzystania, a Wymiana plików na stronie szaleje.

Nie wiem, czy to ma związek ze śmieciarstwem, CZY Co, ale mam problem, żeby to rozgryźć. My window service używa GC" Server " poprzez przełącznik plików konfiguracyjnych widoczny poniżej:
  <runtime>
    <gcServer enabled="true"/>
  </runtime>

Zmiana tej opcji na false nie miała znaczenia. Co więcej, z lektury jaką zrobiłem na nowym GC w 4.0, duże zmiany dotyczą tylko trybu workstation GC, a nie server GC mode. Więc może GC nie ma z tym nic wspólnego.

Pomysły?
Author: RMD, 2011-06-03

5 answers

To było ciekawe.

Główną przyczyną okazuje się zmiana zachowania LocalReport klasy SQL Server Reporting Services (v2010) podczas uruchamiania tego na.Net 4.0.

Zasadniczo Microsoft zmienił zachowanie przetwarzania RDLC tak, że za każdym razem, gdy raport był przetwarzany, robiono to w oddzielnej domenie aplikacji. Zostało to zrobione specjalnie w celu rozwiązania wycieku pamięci spowodowanego niemożnością rozładowania zestawów z domen aplikacji. Kiedy Klasa LocalReport przetwarza plik RDLC, w rzeczywistości tworzy zespół w locie i ładuje go do domeny aplikacji.

W moim przypadku, ze względu na dużą ilość raportów, które przetwarzałem, skutkowało to bardzo dużą liczbą systemów.Runtime.Remoting.Tworzone są obiekty ServerIdentity. To była moja wskazówka do przyczyny, ponieważ byłem zdezorientowany, dlaczego przetwarzanie rldc wymagało remotowania.

Oczywiście, aby wywołać metodę na klasie w innej domenie aplikacji, remoting jest dokładnie czego używasz. W. NET 3.5 nie było to konieczne, ponieważ domyślnie RDLC-assembly został załadowany do tej samej domeny aplikacji. W. NET 4.0 domyślnie tworzona jest nowa domena aplikacji.

Naprawa była dość łatwa. Najpierw musiałem włączyć legacy Security policy używając następującej konfiguracji:
  <runtime>
    <NetFx40_LegacySecurityPolicy enabled="true"/>
  </runtime>

Następnie musiałem wymusić przetwarzanie RDLC w tej samej domenie aplikacji, co moja usługa, wywołując następujące:

myLocalReport.ExecuteReportInCurrentAppDomain(AppDomain.CurrentDomain.Evidence);

To rozwiązało problem.

 82
Author: RMD,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-02 23:42:12

Natknąłem się na ten problem. I to prawda, że domeny aplikacji są tworzone, a nie czyszczone. Jednak nie polecam powrotu do dziedzictwa. Mogą być czyszczone przez ReleaseSandboxAppDomain ().

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

Kilka innych rzeczy, które również robię, aby posprzątać:

Anulowanie subskrypcji zdarzeń podrzędnych, Jasne Źródła Danych, Pozbądź się raportu.

Nasza usługa windows przetwarza kilka raportów na sekundę i nie ma przecieków.

 10
Author: sues999,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-19 06:18:24

You might want to

Być może niektóre API zmieniły semantykę lub może być nawet błąd w wersji 4.0 frameworka

 4
Author: sehe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-02 22:11:58

Dla kompletności, jeśli ktoś szuka równoważnego ustawienia ASP.Net web.config, to jest to:

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

ExecuteReportInCurrentAppDomain działa tak samo.

Dzięki temu Social MSDN reference .

 2
Author: StuartLC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-07 16:24:27

Wygląda na to, że Microsoft próbował umieścić raport w oddzielnej przestrzeni pamięci, aby obejść wszystkie wycieki pamięci, a nie je naprawić. W ten sposób wprowadzili kilka twardych awarii, a skończyło się na większej ilości wycieków pamięci w każdym razie . Wydaje się, że buforują definicję raportu, ale nigdy jej nie używają i nigdy nie czyszczą, a każdy nowy raport tworzy nową definicję raportu, zajmując coraz więcej pamięci.

Bawiłem się robiąc to samo: używaj oddzielnego domeny aplikacji i przesłać do niej raport. Myślę, że jest to okropne rozwiązanie i bardzo szybko robi bałagan.

To, co zrobiłem, jest podobne: podziel część raportowania programu na osobny program do raportów. Okazuje się to dobrym sposobem na uporządkowanie kodu.

Najtrudniejszą częścią jest przekazywanie informacji do oddzielnego programu. Użyj klasy Process, aby uruchomić nową instancję programu reports i przekazać wszelkie potrzebne parametry w wierszu poleceń. Pierwszym parametrem powinna być wartość enum lub podobna wartość wskazująca raport, który ma zostać wydrukowany. Mój kod do tego w głównym programie wygląda mniej więcej tak:

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();
}

A program raportów wygląda tak:

[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);
   }
}

Twoje metody GetReportForm powinny pobrać definicję raportu, użyć odpowiednich argumentów do uzyskania zbioru danych, przekazać dane i wszelkie inne argumenty do raportu, a następnie umieścić raport w przeglądarce raportów w formularzu i zwrócić odniesienie do formularza. Zauważ, że możliwe jest wyodrębnienie dużej części tego procesu, dzięki czemu możesz w zasadzie powiedzieć "podaj mi formularz dla tego raportu z tego zgromadzenia, używając tych danych i tych argumentów".

Zauważ również, że oba programy muszą być w stanie zobaczyć typy danych, które są istotne dla tego projektu, więc miejmy nadzieję, że wyodrębniłeś swoje klasy danych do ich własnej biblioteki, do której oba te programy mogą współdzielić odniesienie. Nie sprawdziłoby się posiadanie wszystkich klas danych w głównym programie, ponieważ miałby kolistą zależność między programem głównym a programem raportowym.

Nie przesadzaj z argumentami. Wykonaj wszelkie zapytania do bazy danych, których potrzebujesz w programie reports; nie przekazuj ogromnej listy obiektów (która i tak by nie działała). Powinieneś po prostu przekazywać proste rzeczy, takie jak pola ID bazy danych, zakresy dat itp. Jeśli masz szczególnie złożone parametry, może być konieczne przepchnięcie tej części interfejsu użytkownika do programu raportów i nie przekazanie ich jako argumentów w wierszu poleceń.

Możesz również umieścić odniesienie do programu raportów w swoim głównym programie i wynikowego .exe i wszelkie pokrewne .biblioteki DLL zostaną skopiowane do tego samego folderu wyjściowego. Następnie można go uruchomić bez podawania ścieżki i po prostu użyć nazwy pliku wykonywalnego samodzielnie (np.: "SomethingReports.exe"). Możesz również usunąć biblioteki DLL raportowania z głównego programu.

Problem polega na tym, że pojawi się oczywisty błąd, jeśli nigdy nie opublikowałeś raportów program. Po prostu dummy opublikować go raz, aby wygenerować manifest, a następnie będzie działać.

Gdy już to działa, bardzo miło jest zobaczyć, że pamięć Twojego zwykłego programu pozostaje stała podczas drukowania raportu. Pojawia się program raportów, który zajmuje więcej pamięci niż główny program, a następnie znika, usuwając go całkowicie, a główny program nie zajmuje więcej pamięci niż już miał.

Innym problemem może być to, że każda instancja raportu będzie teraz zajmować więcej pamięci niż wcześniej, ponieważ obecnie są to całe oddzielne programy. Jeśli użytkownik drukuje wiele raportów i nigdy ich nie zamyka, bardzo szybko zużywa dużo pamięci. Ale myślę, że jest to nadal dużo lepiej, ponieważ pamięć ta może być łatwo odzyskane po prostu zamykając raporty.

To również sprawia, że Twoje raporty są niezależne od głównego programu. Mogą pozostać otwarte nawet po zamknięciu głównego programu i można je wygenerować ręcznie z linii poleceń lub z innych źródeł.

 1
Author: Dave Cousineau,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-25 17:14:25