[C#] gdi+ i zwalnianie zasobów


(Anddezr+Dobreprogramy Pl) #1

Wiem jak zwalniać zasoby zajmowane przez pędzle, pióra itd.

Utworzyłem własną kontrolkę (public class CustomControl: Control), na niej wykonane zostały pewne operacje rysowania (tekst, obrazki) w zdarzeniu Paint (protected override void OnPaint(PaintEventArgs e)).

W trybie wizualnym kontrolkę tą przesunąłem na Formę.

Mogę oczywiście po zakończeniu rysowania w zdarzeniu Paint klasy CustomControl wywołać bmp.Dispose() itd, ale w przykładach gdzie mam animacje tworzenie i zwalnianie zasobów w Paint, które wywoływane jest co chwilę nie ma sensu.

Moje pytanie polega na tym czy zasoby zajmowane przez obiekty (Graphics, Bitmap, Pen, Brush itd) przez klasę CustomControl mogę zwolnić przy zamykaniu Formularza (Form1)?

Utworzyłem w tej klasie funkcję:

public void zwolnij()

		{

			bmp.Dispose();

                        pen.Dispose();

		}

a w form1 przy zamykaniu

private void Form1_FormClosing(object sender, FormClosingEventArgs e)

		{

		  CustomControl.zwolnij();	 

		}

Wszystko działa, ale czy zasoby zostały zwolnione? Czy to jest dobre rozwiązanie?


([alex]) #2

Radziłbym dać zwolnienie w destruktorze. Nie zawsze zamykanie formatki jednoznacznie związane z jej niszczeniem, na przykład odpalenie w trybie modalnym.


(pebal) #3

Tak

Tak

Tak.

Zasoby zwalniaj jednak w obsłudze zdarzenia FormClosed, gdyż to zdarzenie jest generowane już po zamknięciu formy.


([alex]) #4

Nie zupełnie to tak jest. To zdarzenie może zostać odwołane przez zmianę właściwości Cancel na true w parametrze CancelEventArgs przekazanym do zdarzenia. Jeżeli mamy pewną hierarchie okienek np MDI to może się okazać że zamknięcie zostanie "odwołane" przez jakieś okienko podrzędne, zaś okienko główne już zwolni zasoby. Owszem to niezbyt często zdarzający się scenariusz ale wg mnie lepiej dmuchać na zimno tym bardziej że wszystkie obiekty Pen, Brush itp są threadsafety więc bezpiecznie mogą zostać zwolnione w destruktorze klasy.


(Anddezr+Dobreprogramy Pl) #5

Dzięki za odpowiedzi. Alex , w destruktorze klasy czyli jak? Tutaj http://www.go4expert.com/forums/showthread.php?t=1469 przeczytałem, że user nie ma kontroli nad destruktorem. Więc jak i gdzie to zastosować?


([alex]) #6

Nie masz kontroli nad jego wywołaniem - jest wywoływany automagicznie. Natomiast możesz dodać do destruktora co ci się podoba.


(Anddezr+Dobreprogramy Pl) #7

A co gdyby w klasie CustomControl przeciążyć metodę Dispose() tzn

protected override void Dispose( bool disposing )

    {

      if( disposing )

      {

        if (components != null) 

        {

          components.Dispose();

        }

      }

[u] pen1.Dispose();

      mybitmap.Dispose();

      mystringfromat.Dispose();

      brush1.Dispose();[/u]

      base.Dispose( disposing );

    }

Czy wywołanie jej w zdarzeniu FormClosed() Form1 załatwi sprawę, czy fragment zawarty pomiędzy zwalniający poszczególne obiekty jest wymagany, czy też ta metoda sama załatwi sprawę?


([alex]) #8

Jedynym poprawnym logicznie rozwiązaniem jest zwolnienie zasobów w destruktorze, właśnie po to istnieje.

Dispose() jest wywoływane dwukrotnie, więc jeżeli już koniecznie tam to chcesz zrobić to wstaw to wewnątrz if(disposing) lub w else.

Ale wg mnie nie jest to poronione podejście, już lepiej zrób to w FormClosing().


(Anddezr+Dobreprogramy Pl) #9

Dzięki.


(pebal) #10

Zdarzenie FormClosed jest generowane tylko raz, po zamknięciu okna i wtedy należy zwalniać zasoby. Zdarzenie FormClosing może być generowane wielokrotnie.

Destruktorem w języku C# jest metoda Dispose. Metoda ta nie jest automatycznie wołana po zamknięciu formy i aby bezzwłocznie zwolnić zasoby, trzeba ją wołać ręcznie.


([alex]) #11

O tak, a metodą Dispose() w C# jest konstruktor, a konstruktor w C# jest czym?

Jak się nie znasz na temacie to lepiej nie ściemniaj.


(Somekindsoftware) #12

1) Destruktor w C# jest sprowadzany do metody Finalize(), nie Dispose().

2) Metoda Dispose() pochodzi z interfejsu IDisposable() i jest sensownym miejscem zwalniania zasobów, gdy jest taka potrzeba. Obiekty klas implementujących IDisposable można ładnie obsługiwać w sekcjach using.

3) Finalize() zrobi to, co Dispose(), gdy zostanie wywołana przez GC. To on odpowiada za zarządzanie pamięcią, nie programista.

4) Nie ma sensu zwalniania zasobów zarządzanych, tym zajmuje się GC.

5) Problem jest sztuczny - klasa Control implementuje metodę Dispose(), zapewnia zatem zwolnienie swoich zasobów z automatu, gdy zajdzie taka potrzeba. Niczego nie trzeba dodatkowo zwalniać.


([alex]) #13

W tym Pen i Brush ?


(pebal) #14

Co ty za głupoty wypisujesz. Jak widać, nie masz pojęcia o czym piszesz.


([alex]) #15

To ty głupoty wypisujesz, ponieważ destruktorem w C# jest destruktor a nie metoda Dispose(). Oprócz tego że nie masz pojęcia o czym piszesz , to jeszcze sarkazmu nie pojmujesz.


(pebal) #16

Finalizer nie jest destruktorem.

W języku C# nie ma destruktora jako takiego, jego rolę pełni metoda Dispose. W języku C++/CLI masz destruktor i finalizer, tak więc finalizer nie jest destruktorem.

GC nie powinien odpowiadać za zwalnianie innych zasobów niż pamięć.

Bzdura, gdyż GC działa kiedy mu się podoba. Nie można na nim polegać w przypadku zwalniania ograniczonych zasobów.

Problem nie jest sztuczny, gdyż klasa Control zwalnia tylko swoje zasoby. Takich zasobów jak Pen czy Brush nie zwolni.


(Anddezr+Dobreprogramy Pl) #17

W normalnym rysowaniu używam instrukcji using(), ale nie wydaje mi się dobrym pomysłem (tak sądzę), aby w zdarzeniu OnPaint() wywoływanym co 10ms przez timer stosować using() dla ok 10 różnych obiektów graficznych, aby co chwilę były one tworzone i usuwane. Chyba że nie tak jak trzeba to pojmuję.

Czyli obiekt mogę utworzyć na poziomie klasy

private static Pen p = new Pen(Color.White, 5);

a w destruktorze

~CustomControl() { p.Dispose(); }

go usunąć i to załatwi sprawę?


(pebal) #18

Finalizer nie jest destruktorem, gdyż nie spełnia jego wymagań. Finalizer w przeciwieństwie do destruktora, może być wołany wielokrotnie.

Język C# nie posiada destruktora, jego rolę pełni metoda Dispose.


([alex]) #19

To może poczytaj trochę o C# zanim zaczniesz na ten temat się wypowiadać: MSDN: Destructors (C# Programming Guide)

Bardzo niedobre podejście, zabierz ten static i będzie dobrze. static oznacza jeden egzemplarz na wszystkie obiekty tej klasy. Jeżeli będziesz miał dwie takie kontrolki na różnych formatkach to zamknięcie jednej formatki spowoduje zwolnienie obiektu "p" ponieważ istnieje tylko jeden.


(pebal) #20

Jeżeli tymi obiektami nie są duże bitmapy, to śmiało można tworzyć je podczas rysowania.

Nie możesz na takim kodzie polegać, gdyż nie wiadomo kiedy finalizer się wykona.

Nie muszę czytać dokumentów, gdyż język C# bardzo dobrze znam. To, że Microsoft w języku C# pomieszał destruktor z finalizerem, jest faktem bardzo dobrze znanym. Widać to bardzo dobrze na przykładzie języka C++/CLI, gdzie finalizer nie jest destruktorem.

Sam się dokształć a potem pouczaj innych.