+ All Categories
Home > Documents > curs .NET 6.pdf

curs .NET 6.pdf

Date post: 29-Nov-2015
Category:
Upload: andera4u
View: 121 times
Download: 2 times
Share this document with a friend
Description:
curs .NET part 6
21
Garbage Collection - GC Creare obiecte, gestiune memorie heap, eliberare memorie heap (distrugere obiecte). Etapele necesare pentru a accesa o resursa: 1. Creare resursa (operatorul new). 2. Initializare memorie pentru a seta starea initiala a obiectului inainte de a o utiliza (operatiile se realizeaza in constructor). 3. Utilizare resursa prin accesarea membrilor tipului respectiv. 4. Eliberare resursa (eliberarea zonei de memorie ocupata de resursa). Managementul automat al memoriei incearca sa rezolve problemele legate de accesarea obiectelor inexistente (bug de programare) precum si mentinerea memoriei heap intr-o stare consistenta (pierderi de memorie la nivel de aplicatie). Programtorul este degrevat de sarcina gestionarii memoriei; de momentul cand va trebui sa elibereze memoria ocupata de un anumit obiect. Programatorul creaza obiectul, il foloseste si « uneori » mai scrie si cod pentru eliminarea obiectului din memorie. Garbage collector nu are informatii suficiente (mai bine zis complete, pentru ca stie o multime de lucruri din metadata) despre resursa ce reprezinta un anumit tip in memorie. Pentru ca o resursa sa se poate elibera corect din memorie, dezvoltatorul trebuie sa scrie cod in metodele Finalize, Dispose, si Close. Tipurile preconstruite, cum ar fi Int32, Point, Rectangle, String, ArrayList si SerializationInfo, reprezinta resurse ce nu au nevoie de cod special pentru a fi eliberate din memorie. Tipurile ce reprezinta (sau wrap) resurse negestionate (unmanaged) cum ar fi fisiere, socheturi, mutexuri, bitmap, etc., au nevoie de executia unui cod cand obiectul este distrus. Alocare memorie si initializare resurse. Cerinta principala a CLR-ului este ca toate resursele sa fie alocate intr-o memorie heap numita managead heap. Din aceasta memorie managed heap, dezvoltatorul nu va elibera obiectele, acestea vor fi dealocate automat cand aplicatia nu mai are nevoie de ele. Cand stie managed heap ca un obiect nu mai este necesar in aplicatie si trebuie eliberat? Exista mai multi algoritmi pentru a realiza acest lucru, fiecare lucrand bine intr-un anumit mediu. Cand un proces este initializat, CLR rezerva o zona contigua de memorie (adrese) care initial nu contine nici un obiect. Aceasta zona (heap) este gestionata cu ajutorul uni pointer, numit in cazul de fata, NextObjPtr. Acest pointer indica adresa urmatorului bloc liber in heap, deci unde se va aloca urmatorul obiect nou creat. Initial, NextObjPtr contine adresa de inceput a zonei rezervate pentru heap.
Transcript

Garbage Collection - GC Creare obiecte, gestiune memorie heap, eliberare memorie heap (distrugere obiecte). Etapele necesare pentru a accesa o resursa:

1. Creare resursa (operatorul new). 2. Initializare memorie pentru a seta starea initiala a obiectului inainte de a o utiliza

(operatiile se realizeaza in constructor). 3. Utilizare resursa prin accesarea membrilor tipului respectiv. 4. Eliberare resursa (eliberarea zonei de memorie ocupata de resursa).

Managementul automat al memoriei incearca sa rezolve problemele legate de accesarea obiectelor inexistente (bug de programare) precum si mentinerea memoriei heap intr-o stare consistenta (pierderi de memorie la nivel de aplicatie). Programtorul este degrevat de sarcina gestionarii memoriei; de momentul cand va trebui sa elibereze memoria ocupata de un anumit obiect. Programatorul creaza obiectul, il foloseste si « uneori » mai scrie si cod pentru eliminarea obiectului din memorie. Garbage collector nu are informatii suficiente (mai bine zis complete, pentru ca stie o multime de lucruri din metadata) despre resursa ce reprezinta un anumit tip in memorie. Pentru ca o resursa sa se poate elibera corect din memorie, dezvoltatorul trebuie sa scrie cod in metodele Finalize, Dispose, si Close. Tipurile preconstruite, cum ar fi Int32, Point, Rectangle, String, ArrayList si SerializationInfo, reprezinta resurse ce nu au nevoie de cod special pentru a fi eliberate din memorie. Tipurile ce reprezinta (sau wrap) resurse negestionate (unmanaged) cum ar fi fisiere, socheturi, mutexuri, bitmap, etc., au nevoie de executia unui cod cand obiectul este distrus. Alocare memorie si initializare resurse. Cerinta principala a CLR-ului este ca toate resursele sa fie alocate intr-o memorie heap numita managead heap. Din aceasta memorie managed heap, dezvoltatorul nu va elibera obiectele, acestea vor fi dealocate automat cand aplicatia nu mai are nevoie de ele. Cand stie managed heap ca un obiect nu mai este necesar in aplicatie si trebuie eliberat? Exista mai multi algoritmi pentru a realiza acest lucru, fiecare lucrand bine intr-un anumit mediu. Cand un proces este initializat, CLR rezerva o zona contigua de memorie (adrese) care initial nu contine nici un obiect. Aceasta zona (heap) este gestionata cu ajutorul uni pointer, numit in cazul de fata, NextObjPtr. Acest pointer indica adresa urmatorului bloc liber in heap, deci unde se va aloca urmatorul obiect nou creat. Initial, NextObjPtr contine adresa de inceput a zonei rezervate pentru heap.

Operatorul new este folosit pentru alocarea unui obiect in heap, iar in IL (limbaj intermediar) ii corespunde instructiunea newobj. CLR-ul executa urmatorele lucruri cand se creaza un nou obiect:

1. Calculeaza numarul de octeti necesari pentru noul tip si toate tipurile sale de baza ;

2. fiecare obiect mentine doua campuri speciale (32 biti lungime): un camp ce mentine un pointer la o tabela de pointeri la metode si un SyncBlockIndex. Pe un sistem pe 32 biti se mai adauga in plus 8 octeti pentru fiecare obiect, iar pe un sistem 64-biti se adauga 16 octeti pentru fiecare obiect.

3. Verifica daca exista memorie suficienta in heap. Daca exista memorie, obiectul este alocat la adresa data de NextObjPtr.

4. Se apeleaza constructorul obiectului (valoarea lui this este egala cu valoarea lui NextObjPtr) si se obtine adresa unde este memorat obiectul (pointerul this).

5. Se modifica valoarea lui NextObjPtr, astfel incat acesta sa puncteze la urmatoarea zona de memorie, libera.

Dupa cum se observa din figura, intre obiecte nu exista spatiu de memorie nefolosit. Una din caracteristicile « managed heap » este si aceasta de a memora obiectele unul dupa altul fara zone libere de memorie intre obiecte (zona contigua de obiecte). Din punctul de vedere al « memoriei gestionate » (managed heap) aceasta presupune ca spatiul de adrese de memorare este infinit. Din cauza ca acest lucru nu este posibil, s-a implementat un mecanism ce tine sub control aceasta presupunere : garbage collector. In principiu acest mecanism asigura intretinerea zonei heap astfel incat obiectele ce nu mai sunt necesare aplicatiei sunt eliminate si memoria este compactata. Ideea generala pentru GC : este apelat in momentul cand se cere alocarea unui nou obiect in heap managed si nu mai exista loc. In realitate mecanismul este foarte sofisticat si supus mereu schimbarii. Acesta poate fi implementat ca un fir de executie ce se executa in background, fir ce are o prioritate scazuta. Prioritatea firului poate creste in momentele critice, cand este absolut necesara compactarea memoriei. Obiectele din heap au asociate anumite atribute folosite de algoritmul implementat in GC. De exemplu obiectele pot impartite in « clase » (generatii) pentru a face diferenta intre obiectele vechi si cele noi. De asemenea obiectele pastreaza o stare ce poate indica faptul ca acestea nu mai sunt necesare (ar fi trebuit sa se execute destructorul) dar ele inca persista in aceasta zona. Algoritmul Garbage Collection

GC verifica daca exista in heap obiecte ce nu mai sunt utilizate de aplicatie, iar in caz afirmativ obiectele sunt eliberate si memoria compactata. In cazul cand nu mai exista memorie heap disponibila se lanseaza exceptia OutOfMemoryException. Fiecare aplicatie are o multime de “radacini” (puncte de intrare) in heap. Un punct de intrare contine un pointer la un tip referinta. Acest pointer poate referi un obiect din heap sau poate fi null. Toate variabilele globale si locale, variabilele statice, de tip referinta sunt considerate puncte de intrare (radacini) in heap, de asemenea variabilele parametru de tip referinta de pe stiva unui fir sunt considerate puncte de intrare in heap. In interiorul unei metode, un registru CPU ce se refera la un obiect de tip referinta, este considerat punct de intrare. Compilatorul JIT creaza tabele interne in momentul cand compileaza o metoda (cod IL), iar punctele de intrare in aceste tabele contin adrese sau registri CPU ce contin puncte de intrare in heap. Folosind aceste tabele si alte informatii pentru obiectele din heap, GC poate determina daca o anumita zona din heap contine un obiect valid sau nu. De asemenea GC are acces la stiva firului si o poate examina pentru a determina daca exista metode ce fac referire la obiecte din heap. GC presupune ca toate obiectele din heap sunt invalide (adica trebuies eliminate). Se construieste un graf al tuturor punctelor de intrare in heap pentru obiectele valide. Ceea ce nu se gaseste in acest graf se considera ca sunt obiecte ce trebuiesc eliminate. In continuare GC parcurge liniar memoria heap si va elimina obiectele ce nu sunt in graf (algoritmul este foarte simplificat) compactind in acelasi timp memoria heap. Compactarea memoriei heap de catre GC are ca efect modificarea tabelei ce contine puncte de intrare in heap, in caz contrar am avea referinte la obiecte ce nu mai sunt la adresa corecta (daca un obiect contine un pointer la alt obiect se va modifica si valoarea acestui pointer). In final NextObjPtr va puncta la prima zona de meorie libera (memorie organizata in mod liniar). Din punctul de vedere al programatorului, exista anumite avantaje. Acesta nu mai trebuie sa scrie cod pentru a gestiona timpul de viata al obiectului. Urmatorul cod arata cum sunt alocate si gestionate obiectele in heap. class App { static void Main() {

// ArrayList object created in heap, a is now a root. ArrayList a = new ArrayList(); // Create 10000 objects in the heap. for (Int32 x = 0; x < 10000; x++) {

a.Add(new Object()); // Object created in heap } // Right now, a is a root (on the thread’s stack). So a is // reachable and the 10000 objects it refers to are reachable. Console.WriteLine(a.Length);

// After a.Length returns, a isn’t referred to in the code and is no // longer a root. If another thread were to start a garbage collection // before the result of a.Length were passed to WriteLine, the 10001

// objects would have their memory reclaimed. Console.WriteLine("End of method"); }

}

CLR foloseste metadata pentru a determina membrii unui obiect ce se refera la alte obiecte. Finalizare Orice tip ce incapsuleaza (wrap) o resursa negestionata (unmanaged), cum ar fi fisier, obiect nucleu mutex, socket, etc., trebuie sa suporte finalizarea, adica sa ofere resursei eliberarea corecta cand aceasta va fi supusa garbage collection (colectata pentru eliminare). Pentru aceasta tipul implementeaza o metoda numita Finalize. Cand GC determina ca un obiect trebuie eliminat din memorie, acesta apeleaza metoda Finalize a obiectului, daca aceasta exista. In metoda Finalize vom apela metoda corespunzatoare pentru “eliberarea resursei”, pentru fisiere sau obiecte nucleu aceasta metoa poate avea numele CloseHandle. Pentru tipurile ce folosesc resurse unmanaged este obligatoriu de a defini aceasta metoda Finalize, in caz contrar resursa nu este eliberata corect din memorie, si vom avea “pierderi” de memorie ce vor fi rezolvate de catre SO la terminarea procesului (vezi ceva asemanator la obiecte nucleu). Urmatoarul exemplu arata cum ar trebui scris codul din metoda Finalize. public sealed class OSHandle {

// This field holds the Win32 handle of the unmanaged resource. private IntPtr handle; // This constructor initializes the handle. public OSHandle(IntPtr handle) {

this.handle = handle;

} // When garbage collected, the Finalize method, which // will close the unmanaged resource’s handle, is called. protected override void Finalize() {

try {

CloseHandle(handle); } catch() { Console.WriteLine(“Nu putem elibera resursa!!!”); } finally {

base.Finalize(); }

}

// Public method returns the value of the wrapped handle

public IntPtr ToHandle() {

return handle; } // Public implicit cast operator returns the value of the wrapped

handle

public static implicit operator IntPtr(OSHandle osHandle) {

return osHandle.ToHandle(); } // Private method called to free the unmanaged resource [System.Runtime.InteropServices.DllImport("Kernel32")] private extern static Boolean CloseHandle(IntPtr handle);

}

Acest cod nu se compileaza. Do not override Finalize object.Finalize. Instead provide a destructor. Explicatii : Cand obiectul OSHandle este supus procesului de curatare (obiectul in acest moment nu mai este valid pentru aplicatie, dar pentru GC este inca valid), GC va apela metoda Finalize a acestui obiect. In blocul finally se apeleaza metoda de baza Finalize, adica cea din System.Object, aceasta asigurand ca acest cod se va executa chiar daca a aparut o exceptie (nu si in CLR !).

Pentru a inlatura eventualele codari eronate in metoda Finalize (netratarea exceptiilor si neapelarea metodei System.Object.Finalize) s-a definit o sintaxa speciala pentru aceasta metoda. Sa urmarim codul urmator care este aproape identic cu cel anterior. public sealed class OSHandle {

// This field holds the Win32 handle of the unmanaged resource. private IntPtr handle; // This constructor initializes the handle. public OSHandle(IntPtr handle) {

this.handle = handle; }

// When garbage collected, the destructor (Finalize) method,

which // will close the unmanaged resource’s handle, is called. ~OSHandle() {

CloseHandle(handle); }

// Public method returns the value of the wrapped handle

public IntPtr ToHandle() {

return handle; }

// Public implicit cast operator returns the value of the wrapped

handle public static implicit operator IntPtr(OSHandle osHandle) {

return osHandle.ToHandle(); }

// Private method called to free the unmanaged resource [System.Runtime.InteropServices.DllImport("Kernel32")] private extern static Boolean CloseHandle(IntPtr handle);

}

Observatii Aici apare o confuzie in utilizarea acestui destructor pentru OSHandle. Daca se examineaza codul IL, atunci acesta apare ca cel descris in primul exemplu.

Pentru a crea o instanta a obiectului OSHandle, vom apela o functie Win32 ce returneaza un handle la o resursa unmanaged, de ex. CreateFile, CreateMutex, CreateSemaphore, CreateEvent, etc. Apoi folosim operatorul new din C# pentru a construi o instanta pentru OSHandle, pasind ca parametru in ctor acest handle. Cand acest obiect va fi tratat de GC, GC va « vedea » ca exista o metoda Finalize implementata si o va apela, ceea ce va permite eliberarea resursei nucleu (vezi si gestionarea timpului de viata al unui obiect nucleu, se va apela CloseHandle()). CLR nu suporta (inca) distrugerea determinista a unui obiect (nu putem scrie cod pentru a distruge obiectul). Finalize va fi apelata numai de GC, iar cand GC se executa nu stim si nu putem programa acest lucru. Observatie Nu se recomanda folosirea metodei Finalize cand se dezvolta un tip. Timpul procesor pentru GC va creste caci trebuie sa apeleze Finalize pe obiectele pe care le va distruge si pe toate obiectele la care face referire un alt obiect ce are metoda Finalize implementata. De exemplu, pentru un tablou cu 10000 de obiecte pe fiecare obiect se va apela metoda Finalize cand acesta va fi in atentia GC. Pentru resursele unmanaged este obligatorie folosirea metodei Finalize. Intern se construieste o lista cu obiectele ce implementeaza metoda Finalize. GC va trebui sa consulte si aceata lista. Nu trebuie scris cod in Finalize ce acceseaza obiecte interne ale obiectului curent, membrii pentru asemenea obiecte. De exemplu avem un obiect, numit Extern, ce contine un pointer la un alt obiect, numit Intern. Deoarece GC nu apeleaza intr-o ordine determinista metoda Finalize, este posibil sa se apeleze Finalize pe obiectul Intern in timp ce obiectul extern este in « viata ». Nu avem nici un control asupra ordinii executiei metodelor Finalize. In acest moment pointerul din obiectul Extern la obiectul Intern este invalid. Codul din Finalize trebuie sa se execute foarte rapid (asemanator ca la sectiuni critice). Nu trebuie scris cod pentru sincronizare in cadrul acestei metode. In Finalize trebuiesc tratate exceptiile. In Finalize nu trebuie sa facem referire la obiecte managed sau metode statice managed, este posibil ca aceste obiecte sa nu mai existe sau metodele statice sa faca referire la obiecte ce nu mai exista. Urmatorul exemplu emite un sunet cand GC executa o colectie. public sealed class GCBeep {

~GCBeep() {

// We’re being finalized, beep. MessageBeep(-1); // If the AppDomain isn’t unloading, create a new object // that will get finalized at the next collection. // I’ll discuss IsFinalizingForUnload in the next section.

if (!AppDomain.CurrentDomain.IsFinalizingForUnload())

new GCBeep(); }

[System.Runtime.InteropServices.DllImport("User32.dll")] private extern static Boolean MessageBeep(Int32 uType);

}

class App {

static void Main() {

// Constructing a single GCBeep object causes a beep to // occur every time a garbage collection starts. new GCBeep();

// Construct a lot of 100-byte objects.

for (Int32 x = 0; x < 10000; x++) {

Console.WriteLine(x); Byte[] b = new Byte[100];

} }

}

Metoda Finalize va fi apelata chiar daca ctor instantei tipului arunca o exceptie. Deci metoda Finalize nu trebuie sa presupuna ca obiectul este intr-o stare buna, consistenta. Exemplu class TempFile {

String filename = null; public FileStream fs; public TempFile(String filename) {

// The following line might throw an exception. fs = new FileStream(filename, FileMode.Create); // Save the name of this file. this.filename = filename;

}

~TempFile() {

// The right thing to do here is to test filename // against null because you can’t be sure that // filename was initialized in the constructor. if (filename != null) File.Delete(filename);

} }

Acelasi exemplu tratat altfel. class TempFile {

String filename; public FileStream fs; public TempFile(String filename) { try {

// The following line might throw an exception. fs = new FileStream(filename, FileMode.Create); // Save the name of this file. this.filename = filename;

} catch {

// If anything goes wrong, tell the garbage collector // not to call the Finalize method. GC.SuppressFinalize(this); // Let the caller know something failed. throw;

} } ~TempFile() {

// No if statement is necessary now because this code // executes only if the constructor ran successfully.

File.Delete(filename);

}

}

Cauzele ce produc apelul metodei Finalize

1. Generatia 0 este plina. Acest eveniment este calea cea mai obisnuita de a apela metoda Finalize, pentru ca acesta apare cand aplicatia se executa si are nevoie sa aloce noi obiecte.

2. Codul apeleaza explicit metoda statica Collect din System.GC. Codul poate cere in mod explicit CLR-ului sa execute o colectie. MS nu recomanda apelul acestei metode.

3. CLR descarca un AppDomain. Cand se intimpla acest lucru, CLR considera ca nu exista intrari in heap in cadrul AppDomain si apeleaza Finalize pentru obiectele ce au fost create in AppDomain.

4. CLR se inchide (shuting down). Cand un proces se termina OK, se incearca terminarea corecta a CLR-ului. In acest moment CLR considera ca nu exista puncte de intrare in heap in cadrul procesului si apeleaza metoda Finalize pentru obiectele din heap managed.

CLR foloseste un fir dedicat pentru a apela metodele Finalize. Pentru primele trei evenimente metoda Finalize intra intr-o bucla infinita, firul este blocat si nu se mai apeleaza nici o metoda Finalize. Este un dezastru. Pentru evenimentul 4, fiecare metoda Finalize consuma aproximativ 2 secunde pentru executie. Daca nu se intimpla asa, CLR omoara procesul si nu se mai apeleaza alte metode Finalize. De asemenea, daca CLR consuma mai mult de 40 secunde pentru a apela o metoda Finalize a unui obiect, CLR omoara procesul. Valorile scrie mai sus pot fi schimbate de MS in cadrul procesului de imbunatatire al algoritmului pentru GC. Codul din Finalize poate construi alte obiecte; daca se intimpla asta in timp ce CLR se termina (shutdown) CLR va continua sa colecteze obiecte si sa apeleze metodele lor Finalize pina cand nu mai exista nici un obiect sau au trecut 40 de secunde. Reluam exemplul GCBeep. Daca un obiect GCBeep este pe cale sa se finalizeze din cauza evenimentului 1 sau 2, un nou obiect GCBeep este construit. Este corect pentru ca aplicatia continua sa ruleze, presupunind ca mai multe colectii vor fi in viitor. Daca un obiect GCBeep este pe cale sa se finalizeze din cauza evenimentului 3 sau 4, un nou obiect nu va putea fi construit pentru ca acest obiect ar trebui sa fie creat in timp ce se descarca AppDomain sau CLR a fost oprit. Daca aceste noi obiecte au fost create, CLR ar trebui sa poata apela Finalize pentru aceste obiecte. Pentru a preveni constructia acestor noi obiecte GCBeep, metoda Finalize din GCBeep apeleaza metoda AppDomain.IsFinalizingForUnload. Aceasta metoda returneaza TRUE daca metoda Finalize a obiectului este apelata pentru ca AppDomain se descarca din memorie. Solutia de mai sus este buna numai pentru AppDomain. Pentru CLR MS a adaugat proprietatea read-only HasShutdownStarted la clasa System.Environment. Metoda Finalize din GCBeep ar trebui sa citeasca aceasta proprietate si daca este TRUE sa nu mai permita crearea de noi obiecte GCBeep.

Proprietatea este inaccesibila, pentru ca a fost implementata eronat, a fost implementata ca o proprietate ce tine de instanta clasei, iar clasa Environment are un ctor privat si deci nu putem construi obiecte din aceasta clasa. Pina la modificarea bibliotecii FCL nu exista o cale de a sti daca CLR este pe cale de terminare sau nu. Finalizare – probleme interne Sa ne reamintim ca obiectele ce au implementata metoda Finalize sunt pastrate intr-o lista separata, lista de finalizare, gestionata de GC. Obiectele ce nu implementeaza propria metoda Finalize nu sunt trecute in lista de finalizare, chiar daca sunt derivate din System.Object ce are metoda Finalize. Sunt doua actiuni (operatii) importante ce trebuiesc facute : alocare memorie pentru obiectul creat si inscrierea obiectului in aceasta lista. In fapt putem considera ca operatie elementara o operatie care la rindul ei este critica. Putem distinge astfel de operatii: alocare memorie, completare lista finalizare, construire obiect (apelare constructor). Ordinea acestor operatii (conform documentatiei MS) este : alocare memorie, completare lista finalizare, apelare constructor. Fiecare din aceste operatii poate sa esueze. Pentru fiecare situatie in parte trebuiesc luate masuri speciale in scrierea codului. Daca obiectul nu este construit din diverse motive (deci ultima operatie), atunci sistemul trebuie sa fie capabil sa faca rollback pe operatiile anterioare. Cel mai important este eliminarea obiectului din lista de finalizare. In rest ramane treaba GC sa rezolve problema cu memoria alocata si nefolosita.

Sa consideram exemplul din figura de mai sus. Cand se executa GC, obiectele B, E, G, H, I, si J nu mai constituie puncte valide de intrare in heap (radacini) si deci vor fi supuse procesului de colectare si eliminare. Pentru fiecare obiect se cauta daca exista in lista de finalizare, iar in caz afirmativ este eliminata intrarea din aceasta lista si trecuta intr-o alta lista F-reachable si asta inseamna ca pentru obiectele de aici se va apela metoda Finalize.

Dupa colectie managed heap arata astfel :

Un fir dedicat din CLR, va apela metodele Finalize. Un fir dedicat este folosit pentru a preveni eventualele probleme legate de sincronizare. Cand coada F este vida, acest fir este in asteptare. El va fi lansat numai pe evenimente ce spun ca sunt obiecte in coada F. Obiectele ce au intrari in coada F nu sunt supuse procesului de eliminare, ele constituie inca radacini valide pentru heap managed. Memoria heap managed dupa etapa a doua a colectarii si eliminarii arata astfel:

Paternul Dispose: Fortarea unui obiect sa se stearga Metoda Finalize se foloseste in special pentru resurse unmanaged. Metoda nu poate fi apelata direct. Tipurile ce ofera posibilitatea de a fi in mod determinist “dispose” sau inchise implementeaza ceea ce se numeste “dispose pattern”. Dispose pattern – defineste conventiile la care un dezvoltator ar trebui sa adere in momentul cand defineste un tip ce doreste sa poata fi sters in mod explicit. Pentru un tip ce implementeaza dispose pattern, utilizatorul va putea elimina obiectul exact atunci cand nu mai are nevoie de el.

Un tip poate contine atat Finalize cat si Dispose. Exemplu : clasa System.IO.BinaryWriter intra in aceasta categorie. Exemplul urmator descrie o versiune mai buna pentru tipul OSHandle. using System; // Implementing the IDisposable interface signals users of // this class that it offers the dispose pattern. public sealed class OSHandle : IDisposable {

// This field holds the Win32 handle of the unmanaged resource. private IntPtr handle; // This constructor initializes the handle. public OSHandle(IntPtr handle) {

this.handle = handle; }

// When garbage collected, this Finalize method, which // will close the unmanaged resource’s handle, is called. ~OSHandle() {

Dispose(false); } // This public method can be called to deterministically // close the unmanaged resource’s handle. public void Dispose() {

// Because the object is explicitly cleaned up, stop the // garbage collector from calling the Finalize method. GC.SuppressFinalize(this); // Call the method that actually does the cleanup. Dispose(true);

} // This public method can be called instead of Dispose. public void Close() {

Dispose(); }

// The common method that does the actual cleanup. // Finalize, Dispose, and Close call this method. // Because this class is sealed, this method is private.

// If this class weren’t sealed, this method wouldn’t be protected. private void Dispose(Boolean disposing) {

// Synchronize threads calling Dispose/Close

// simultaneously. lock (this) { if (disposing) {

// The object is being explicitly disposed of/closed, not // finalized. It is therefore safe for code in this if // statement to access fields that reference other // objects because the Finalize method of these other objects // hasn’t yet been called.

// For the OSHandle class, there is nothing to do in here.

} // The object is being disposed of/closed or finalized. if (IsValid) {

// If the handle is valid, close the unmanaged resource. // NOTE: Replace CloseHandle with whatever function is // necessary to close/free your unmanaged resource. CloseHandle(handle); // Set the handle field to some sentinel value. // This precaution prevents the possibility // of calling CloseHandle twice. handle = InvalidHandle;

} }

}

// Public property to return the value of an invalid handle. // NOTE: Make this property return an invalid value for // whatever unmanaged resource you’re using. public IntPtr InvalidHandle {

get { return IntPtr.Zero; } } // Public method to return the value of the wrapped handle public IntPtr ToHandle() { return handle; } // Public implicit cast operator returns the value of the wrapped handle public static implicit operator IntPtr(OSHandle osHandle) {

return osHandle.ToHandle(); }

// Public properties to return whether the wrapped handle is valid. public Boolean IsValid {

get { return (handle != InvalidHandle); } } public Boolean IsInvalid {

get { return !IsValid; } } // Private method called to free the unmanaged resource. [System.Runtime.InteropServices.DllImport("Kernel32")] private extern static Boolean CloseHandle(IntPtr handle);

}

Cam mult cod, dar acest cod se repeta pentru fiecare tip ce foloseste o resursa unmanaged. Explicatii OSHandle implementeaza interfata System.IDisposable. In FCL aceasta este definita astfel: public interface IDisposable {

void Dispose(); }

Pentru ca un tip sa adere la dispose pattern trebuie sa implementeze aceasta interfata. Asta inseamna ca tipul ofera o metoda publica, fara parametri, Dispose, ce poate fi apelata pentru a elibera resursa wrapped de catre obiect. Memoria din heap managed pentru obiect va fi eliberata de GC. OSHandle implementeaza o metoda publica Close, ce apeleaza Dispose. Patternul dispose nu obliga folosirea acestei metode. Metodele fara parametri, Dispose si Close trebuie sa fie publice si nevirtuale. Metoda Dispose este la baza virtuala fiind dintr-o interfata. In tipul nostru se defineste ca fiind public si sealed (nu poate fi folosita in derivare). Compilatorul face implicit asta pentru noi. Metoda Dispose fara parametri va apela metoda Dispose cu un parametru de tip bool, iar codul pentru curatare se va gasi in aceasta ultima metoda. In cadrul metodei Dispose are loc o sincronizare.

Cand se apeleaza metoda Finalize a unui obiect, valoarea parametrului metodei Dispose este false. Asta inseamna ca metoda Dispose nu ar trebui sa execute cod ce face referire la alte obiecte managed. Daca clasa OSHandle nu ar fi fost declara sealed, metoda Dispose(bool) ar fi trebuit sa fie implementata ca protected virtual si nu ca private nonvirtual. Orice clasa ce deriva din OSHandle ar trebui sa implementeze metoda Dispose(boolean) nu si metodele fara parametri Dispose si Close si nu trebuie sa implementeze Finalize. Exemplu de clasa derivata din OSHandle class SomeType : OSHandle {

// This field holds the Win32 handle of the unmanaged resource. private IntPtr handle; protected override void Dispose(Boolean disposing) {

// Synchronize threads calling Dispose/Close simultaneously. lock (this) {

try {

if (disposing) { // The object is being explicitly disposed of/closed, not // finalized. It is therefore safe for code in this if // statement to access fields that reference other // objects because the Finalize method of these other // objects hasn’t been called. // For this class, there is nothing to do in here. } // The object is being disposed of/closed or finalized. if (IsValid) { // If the handle is valid, close the unmanaged resource. // NOTE: Replace CloseHandle with whatever function is // necessary to close/free your unmanaged resource. CloseHandle(handle); // Set the handle field to some sentinel value. This precaution // prevents the possibility of calling CloseHandle twice. handle = InvalidHandle;

}

} finally {

// Let the base class do its cleanup. base.Dispose(disposing);

} }

} } Folosirea unui tip ce implementeaza patternul Dispose Vom explica acest lucru pe baza unui exemplu, folosind clasa System.IO.FileStream. Aceasta clasa ofera posibilitatea de a lucra cu fisiere. Intern aceasta clasa pastreaza un handler, cu acces private, la fisierul cu care lucreaza, handler returnat de functia CreateFile. Vom crea un fisier, in care se scrie ceva si apoi se sterge. using System; using System.IO; class App {

static void Main() {

// Create the bytes to write to the temporary file. Byte[] bytesToWrite = new Byte[] { 1, 2, 3, 4, 5 }; // Create the temporary file. FileStream fs = new FileStream("Temp.dat", FileMode.Create); // Write the bytes to the temporary file. fs.Write(bytesToWrite, 0, bytesToWrite.Length); // Delete the temporary file. File.Delete("Temp.dat"); // Throws an IOException

} }

Dupa executie obtinem urmatoarea exceptie: Unhandled Exception: System.IO.IOException: The process cannot access the file " Temp.dat" because it is being used by another process. at System.IO.__Error.WinIOError(Int32 errorCode, String str) at System.IO.File.Delete(String path) at App.Main() in c:\Documents and Settings\info\My Documents\SharpDevelop Pro jects\p6_gc3\Main.cs:line 25 Press any key to continue . . .

Problema apare in metoda Delete. Exista si sanse ca acest cod sa functioneze. Daca alt fir cauzeaza un GC dupa apelul lui Write si inainte de Delete, obiectul FileStrem va apela metoda Finalize si fisierul va fi inchis. Implementarea patternului Dispose in aceasta clasa inlatura acest bug. Codul in noua varianta poate fi: using System; using System.IO; class App {

static void Main() {

// Create the bytes to write to the temporary file. Byte[] bytesToWrite = new Byte[] { 1, 2, 3, 4, 5 }; // Create the temporary file. FileStream fs = new FileStream("Temp.dat", FileMode.Create); // Write the bytes to the temporary file. fs.Write(bytesToWrite, 0, bytesToWrite.Length); // Explicitly close the file when done writing to it. ((IDisposable) fs).Dispose(); // Delete the temporary file. File.Delete("Temp.dat"); // This always works now.

} }

Diferenta este in apelul:

((IDisposable) fs).Dispose();

care inchide fisierul. Acelasi exemplu, dar inchiderea fisierului se face cu o metoda din clasa FileStream. using System; using System.IO; class App {

static void Main() {

// Create the bytes to write to the temporary file. Byte[] bytesToWrite = new Byte[] { 1, 2, 3, 4, 5 }; // Create the temporary file. FileStream fs = new FileStream("Temp.dat", FileMode.Create);

// Write the bytes to the temporary file. fs.Write(bytesToWrite, 0, bytesToWrite.Length); // Explicitly close the file when done writing to it. fs.Close(); // Delete the temporary file. File.Delete("Temp.dat"); // This always works now.

} }

Important Cand definim propriul tip ce implementeaza patternul Dispose, trebuie sa scriem cod in toate metodele noastre pentru a lansa execeptia System.ObjectDisposedException daca obiectul a fost eliminat in mod explicit. Metodele Dispose si Close nu trebuie sa arunce niciodata aceasta exceptie. Folosirea instructiunii using Exemplu using System; using System.IO; class App {

static void Main() {

// Create the bytes to write to the temporary file. Byte[] bytesToWrite = new Byte[] { 1, 2, 3, 4, 5 }; // Create the temporary file. using (FileStream fs = new FileStream("Temp.dat", FileMode.Create)) {

// Write the bytes to the temporary file. fs.Write(bytesToWrite, 0, bytesToWrite.Length);

} // Delete the temporary file. File.Delete("Temp.dat"); // This always works now.

} }

In using se initializeaza obiectul si se salveaza o referinta la acesta intr-o variabila. Cu aceasta referinta apelam metode din obiect. La intilnirea instructiunii using, compilatorul construieste blocurile try si finally, iar in blocul finally se face cast la IDisposable pentru a apela metoda Dispose. Instructiunea using lucreaza numai cu tipuri ce implementeaza interfata IDisposable. Generatii

Un GC generational (??) face urmatoarele presupuneri: 1. Un obiect este cu atit mai nou cu cit timpul sau de viata este mai mic. 2. Un obiect este cu atit mai vechi cu cit timpul sau de viata este mai mare. 3. Colectarea unei portiuni din heap este mai rapida decat parcurgerea intregului.

La initializare mh (managed heap) nu contine obiecte. Obiectele adaugate se spun ca sunt din generatia 0. La initializare, CLR stabileste marimea zonei pentru generatia 0 (pp 256 KB).

Dupa un timp obiectele C si E nu mai sunt radacini. Cand marimea acestei zonei satbilita de CLR este depasita se lanseaza procesul de colectare. Obiectele ce au fost tratate odata de GC trec in generatia urmatoare. Fiecarei generatii i se stabileste o marime in heap. De la generatie la generatie aceste marimi pot sa difere (cf doc. MS). Noile obiecte ce se creaza pleaca cu generatia 0.

La o noua colectare generatia 1 va deveni 2, 0 va deveni 1. Se presupune ca obiectele cu generatia mare nu au nevoie permanenta de colectare. Oricum cand exista legaturi intre obiecte din generatii diferite, se supun procesului GC si generatiile vechi. Controlul programabil al GC

�������������� �

�������������

� ������������������������������������

����������� ��� ���������������������������� ���� ��� �������

������������ �

�� ����

� ������������������������������������

!"�� ������������������������ ������

����������� !"�� �������#� �������� ��������������� �������������$����

����� ��������

� ������������������������������������

#���"������� �������������� ���� ���� ��������� �������%��������������������������������������������������"� ���������� ������� ������������� ������������������� &�����$�����

'���% "���

� ������������������������������������

#�����������������������$��(���������� �� ���������������� ������������������������ ������� ������������������������������� ����

#�#����������� &���

� ������������������������������������

#�) ������������������ ������� &��������������������������$��(����������� ��������� &���������"� � ��������� ����

� ��������� &���

� ������������������������������������

#�) ��������������������� ������� &��������������������������$����

*����+�������� &�����

� ������������������������������������

� ����������� ����������� � �����������������������) � �������� &�����������������) � ���


Recommended