[C#] Uruchamianie procesów - procesy zostające w pamięci


(Marcin Obala) #1

Witam

Mam pewien problem. Tworzę w programie raport zapisując go do xls. Następnie po otwarciu excela poleceniem

Process.Start("excel");

i zamknięciu excela proces excel.exe zostaje w pamięci. Wykomentowałem kod tworzenia pliku xls i odpaliłem excela i zamknął się więc wnioskuje że w moim kodzie jest coś nie tak (skopiowany z internetu). Poniżej go podaje.

void Button3Click(object sender, EventArgs e)

{

	if(textBox1.Text=="")

		return;

	Excel.Application xlApp ;

	Excel.Workbook xlWorkBook ;

	Excel.Worksheet xlWorkSheet ;

	object misValue = System.Reflection.Missing.Value;


	xlApp = new Excel.ApplicationClass();

	xlWorkBook = xlApp.Workbooks.Add(misValue);


	xlWorkSheet = (Excel.Worksheet)xlWorkBook.Worksheets.get_Item(1);


	string[] kolumny = textBox1.Text.Split(new char[] {'\t','\n','\r'},StringSplitOptions.RemoveEmptyEntries);

	int j=1;

	for(int i=0;i
	{

		xlWorkSheet.Cells[j, 1] = kolumny[i];

		xlWorkSheet.Cells[j, 2] = kolumny[i+1];

		if(i>2)

			if(int.Parse(kolumny[i+1]) < 15)

				xlWorkSheet.get_Range("b"+j.ToString(),"b"+j.ToString()).Interior.Color = Color.Red;


		j++;

		if(i==2)

		{

			xlWorkSheet.Cells[j, 1] = "Product";

			xlWorkSheet.Cells[j, 2] = "Panels";

			j++;

		}


	}


	xlWorkSheet.get_Range("a1", "b2").Font.Bold = true;

	xlWorkSheet.get_Range("a1", "b2").Font.Italic = true;

	xlWorkSheet.get_Range("a1", "b10000").Columns.AutoFit();

	xlWorkSheet.get_Range("a3", "b3").AutoFilter(1, Type.Missing, Excel.XlAutoFilterOperator.xlAnd, Type.Missing, true);

	string file_name="C:\\" +DateTime.Now.ToString().Replace('/','_').Replace(' ', '_').Replace(':','_')+".xls";

	xlWorkBook.SaveAs(file_name, Excel.XlFileFormat.xlWorkbookNormal, misValue, misValue, misValue, misValue, Excel.XlSaveAsAccessMode.xlExclusive, misValue, misValue, misValue, misValue, misValue);

	xlWorkBook.Close(true, misValue, misValue);

	xlApp.Quit();

	Process.Start("excel");

	//MessageBox.Show("Excel file created: " + file_name);

}

Teraz jeszcze sprawdziłem coś, excel.exe powstaje po utworzeniu obiektu xlApp co raczej jest normalne. Jednak czasami podczas testowania programu zamykam aplikację nie przez krzyżyk tylko przez przycisk STOP w VS czy Sharp/Mono Developie i wtedy proces zostaje w pamięci. Moje pytanie, jak zwolnić pamięć i usunąć proces z pamięci po utworzeniu pliku xls?


(Frankfurterium) #2

Metoda Process.Kill();


(Marcin Obala) #3

To nie rozwiązuje problemu tylko likwiduje jego skutki. To jest najmniejsza linia oporu. Lepiej znaleźć przyczynę bólu i ją zlikwidować niż ciągle łykać tabletki przeciwbólowe. Przy bardziej złożonych programach takie coś może mieć niepożądane efekty.

I jeszcze zostaje jedna kwestia. Problem nie powstaje przy tworzeniu procesu tylko przy tworzeniu obiektu

Excel.Application xlApp;

xlApp = new Excel.ApplicationClass();

i go na końcu zamykam

xlApp.Quit();

Jednak mimo tego zostaje w pamięci.

Process.Start() sam w sobie działa dobrze - po wywołaniu tej funkcji mam dwa procesy Excel.exe w pamięci, jak zamknę excela to jeden znika ale drugi nadal wisi. Wyłączenie programu powoduje że proces znika. Ja chcę osiągnąć efekt żeby proces znikał po zapisaniu pliku. W internecie czytałem że to może być coś w stylu że nie może go zamknąć bo coś do niego się nadal odwołuje. Niestety w domu nie mam kodu więc w pracy sprawdzę co się stanie jak ustawię wszystko na null.


(Fiołek) #4

Najwidoczniej Excel.ApplicationClass zamyka proces dopiero przy finalizacji obiektu - obiekt nie ma metody Dispose, więc nie da się tego zastosować. Ustawieni xlApp na null tak naprawdę da efekt dopiero przy uruchomieniu GC(co można wymusić - GC.Collect). Ew. możesz za pomocą refleksji wywołać metodę Finalize/destruktor i poinformować GC, by więcej finalizera nie wywoływał.

Nie mam chwilowo możliwości tego sprawdzić, więc to są całkowite strzały i nie biorę za nie odpowiedzialności :stuck_out_tongue:

EDIT: to cudo używa COM, więc jeszcze inaczej to może działać, ale szedłbym w tym kierunku.


(Tomek Matz) #5

@Fiołek

Jesteś pewien, że GC działa z kodem niezarządzanym?

@Marcin511

Przejrzyj te dwa linki i daj znać, czy znajdziesz w nich to co Cię interesuje:

http://forum.dobreprogramy.pl/tabela-pliku-xls-razem-pierwszym-wierszem-jak-t440136.html

http://csharp.net-informations.com/excel/csharp-open-excel.htm


(Dimatheus) #6

Hej,

Pojawiają się dwa procesy, bo wywołujesz dwie osobne instancje programu. Jest to przydatne, gdy na przykład pracuje się na dwóch monitorach i chcesz mieć część plików otwartych w jednym, a część w drugim oknie. Mając jedną instancję programu przenosisz bowiem wszystkie okna otwartych plików. Schemat takiego otworzenia jest banalny - ilekroć naciśniesz skrót programu, tyle nowych instancji programu otworzysz. Może więc zamiast uruchamiać sam proces excel'a, warto wymusić otwarcie konkretnego pliku - wtedy uruchomi się on w już istniejącej instancji programu, a jeśli tan nie będzie uruchomiony, po prostu zostanie włączony.

Pozdrawiam,

Dimatheus


(Tomek Matz) #7

@Dimatheus

Ja jednak stawiałbym na to, że kod jest błędny. Pierwsze co się w nim rzuca w oczy i co myślę, że autor tematu już poprawił (na podstawie dwóch powyższych linków) to te dwie linijki:

xlApp.Quit();

   Process.Start("excel");

(Dimatheus) #8

Hej,

Wcale nie twierdzę, że tak nie jest. Zwracam tylko uwagę na to, że pojawiają się dwa osobne procesy, a komenda zamyka tylko jeden z nich. A gdyby właśnie otworzyć konkretny plik, a nie program?

Pozdrawiam,

Dimatheus


(Tomek Matz) #9

@Dimatheus

Marcin511 sam otwiera (programowo) te dwa procesy (z jakiegoś powodu?). U mnie uruchamiany jest tylko jeden (nie ma znaczenia przy tym, czy otworzę program, czy konkretny plik).


(Marcin Obala) #10

Nie mam chwilowo czasu żeby to sprawdzić jednak chwilę żeby odpisać mam.

Pierwszy proces uruchamia się przy linijce.

xlApp = new Excel.ApplicationClass();

Drugi przy

Process.Start("excel");

Ten kod wyżej to tylko kod poglądowy. Ja w swoim programie odpalam tak

Process.Start(path);

gdzie path to np. "C:\raport.xls" i żeby nie było nieścisłości. Process.Start() działa prawidłowo. Otwiera się Excel.exe PID 666, klikam krzyżyk, proces Excel.exe PID 666 znika. Jednak mimo wszystko nadal wisi proces uruchomiony przez

xlApp = new Excel.ApplicationClass();

I nie znika on po wywołaniu

xlApp.Quit();

Hmm a co do linków to ja korzystałem z drugiego ale widzę że nie skopiowałem drugiej funkcji private void releaseObject(object obj). Może tutaj leży problem, ale to sprawdzę później.


(Fiołek) #11

ApplicationClass to interfejs na obiekt COM-owy. Nie wiem, gdzie dokładnie jest zmniejszany licznik referencji i, co za tym idzie, niszczony obiekt - strzelałem, że w finalizerze.

Problemem może być używanie ApplicationClass bezpośrednio, zamiast tak, jak to mówi MSDN - za pomocą Application - artykuł KB tłumaczy to: http://support.microsoft.com/kb/302084 .

Na jednej ze stron(http://csharp.net-informations.com/exce ... -excel.htm)*, używane jest Marsha.ReleaseComObject do ręcznego zmniejszenia licznika referencji COM(i przy tym używa ApplicationClass).

EDIT: teraz zauważyłem, że nie byłem pierwszy - mea culpa.


(Tomek Matz) #12

@Fiołek

Z mojej strony to było pytanie retoryczne :stuck_out_tongue: GC szuka pozostałości tylko i wyłącznie po obiektach kodu zarządzanego.


(Fiołek) #13

@up:

Którym niejako jest binding COM-owy(chyba, że to jest automagicznie zamieniane prze kompilator/JITter na coś mniej zarządzanego) :wink:


(Tomek Matz) #14

@Fiołek

Czy mógłbyś podać źródło tej informacji? Ja ze swojej strony mogę podać to:

http://msdn.microsoft.com/en-us/magazine/cc163491.aspx


(Fiołek) #15

Co nie zmienia faktu, że COŚ musi wywołać IUnknown.Release.

Dotarłem do tego: http://en.wikipedia.org/wiki/Runtime_Callable_Wrapper , więc wrapper na COM JEST obiektem .NET-owym i podlega GC(co potwierdza opis na StackOverflow i dalsze cytaty).

http://blogs.msdn.com/b/vcblog/archive/ ... 62884.aspx

http://msdn.microsoft.com/en-us/library/8bwh56xe.aspx


(Tomek Matz) #16

@Fiołek

Nie ukrywam, że trochę się pogubiłem :stuck_out_tongue: Tutaj przy okazji pytanie - mówiąc "binding COM-owy" miałeś na myśli RCW? Do czego zmierzasz? Czyli GC czyści pozostałości po .NET-owych obiektach RCW, a więc COM-owe obiekty (dla których RCW pełni rolę proxy) można traktować tak jak obiekty kodu zarządzanego? Czyli tym samym ręczne wywołanie ReleaseComObject nie jest jednak potrzebne, bo zajmie się tym w tle GC? Przecież nawet w linku, który podałeś (tym, który cytujesz), mówią, żeby wywoływać tą metodę. Ja zawsze byłem święcie przekonany o tym, że to obowiązkiem programisty jest poinformowanie GC (ręczne - patrz Dispose) o tym, że powinien zwolnić zasoby zajmowane przez obiekty kodu niezarządzanego.


(Fiołek) #17

Tak, RCW(bo jakby nie patrzeć zawsze na nim operujemy - to on jest pomostem między kodem niezarządzanym i zarządzanym). Częścią niezarządzaną jest serwer COM, klient, który dostępny jest przez proxy RCW, jest już zarządzany przez .NET i GC. IUnknown::Release jest wywoływane przez finalizer RCW, ale kiedy on się wykona? Wtedy kiedy GC sprzątnie obiekt, co nie musi nastąpić zaraz po utracie referencji, a np. po wyłączeniu programu(bo nie wiemy kiedy GC odzyska pamięć). Więc jeśli chcemy całkowicie pozbyć się obiektu COM zaraz po utracie referencji należy wywołać ReleaseComObject(albo FinalReleaseComObject).

Jeśli to byłby kod niezarządzany to tak, ale to NIE JEST kod niezarządzany(choć trzeba przyznać, że brakuje Dispose) :wink:


(Tomek Matz) #18

@Fiołek

Rozumiem, no ja jednak pozostanę w swoich staroświeckich przekonaniach, że lepiej jest zawsze ręcznie zwalniać zasoby zajmowane przez obiekty kodu niezarządzanego. Ciekawe jest to, że w przypadku metody ReleaseComObject dokumentacja do wersji .NET Framework 3.5 sugeruje dokładnie to samo (na żółto jest to zaznaczone)

http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.releasecomobject%28v=VS.90%29.aspx

, a już w wersji .NET Framework 4 oraz 4.5 jest to ujęte zupełnie inaczej,

http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.releasecomobject%28v=VS.100%29.aspx

choć data aktualizacji dokumentacji .NET Framework 3.5 oraz 4 jest dokładnie ta sama (Updated: December 2010), ogólnie jaja :slight_smile:

Zakładam, że z metodą Dispose postępujesz podobnie, tzn. nigdy jej ręcznie nie wywołujesz (bo w końcu jest implementowana przez obiekty kodu zarządzanego) i zdajesz się na GC. Wiem, że Cię nie przekonam, ale mimo tego podrzucę ten link, który już jakiś czas temu wrzucałem tutaj na forum do dyskusji kiedy powinno się wywoływać tą metodę http://blogs.msdn.com/b/kimhamil/archive/2008/11/05/when-to-call-dispose.aspx.

@Marcin511

Po tym co tu przeczytałeś musisz zrobić jak sam uważasz za słuszne. Ja zostaję przy tym, że jednak powinieneś zwolnić zasoby. Jeśli się na takie rozwiązanie zdecydujesz to kod, do którego podałem link, musisz poprawić. Zwalnianie zasobów musi następować zawsze w sekcji finally bloku try/catch/finally. Ogólnie to miałem nadzieję, że użyjesz tej pierwszej wersji kodu (z pierwszego linka, który podałem), bo tam operujesz na znanych Ci obiektach ADO .NET, no ale mniejsza o to :slight_smile:


(Fiołek) #19

Źle zakładasz :wink: Interfejs IDisposable jest często, gęsto przeze mnie wykorzystywany(i implementowany i używany), po prostu nie do końca widzę sens używania tego przy obiektach COM-owych(tym bardziej, że nie jest implementowany, acz w tym wypadku jak najbardziej bym wymusił zwolnienie RCW) :wink:

Dzięki za link.