alessandro andreose’ - helpdesk website studio e... · il metodo finalize (distruttore) metodo...

Post on 16-Feb-2019

218 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Alessandro Andreose’

Quando? Quando parte il Garbage Collector?

Viene eseguito dal .NET Framework quando lo ritiene necessario

Quando non c’è abbastanza spazio nello heap

Cosa fa? Cosa succede quando il GC viene eseguito?

GC controlla tutti gli oggetti nello heap

Segna tutti gli oggetti raggiungibili direttamente e indirettamente da un riferimento

Rilascia la memoria di tutti gli oggetti non contrassegnati

Ricompatta lo heap

Problema

Finalizzazione non deterministica

Il metodo finalize (Distruttore) Metodo speciale

Il garbage collector invoca questo metodo prima di rilasciare la memoria allocata per l’oggetto

Nel metodo finalize non si deve accedere ad oggetti esterni.

Potrebbero essere già stati distrutti dal GC

NotaIl metodo viene eseguito quando il GC deve rilasciare la memoriaMa io non so quando il GC dovrà fare questa operazione

finalize In c# non è possibile chiamare o sottoporre ad override il metodo

Object.Finalize() In c# i distruttori vengono utilizzati per la scrittura del codice di

finalizzazione

Il distruttore si dichiara con la tilde (~) seguita dal nome della classe

Non ha accessori di visibilità

Non ha parametri

public class Car

{

~Car()

{

// cleanup code

}

}

distruttori ~Car()

{

// cleanup code

}

protected override void Finalize() {

try {

// cleanup code

}

finally {

base.Finalize();

}

}

Trasformato dal compilatore

NotaIn questo modo il metodo Finalize viene richiamato in modo ricorsivoper tutte le istanze nella catena di ereditarietà, dalla più derivata alla meno derivata

Nota (I) Si consiglia di non utilizzare distruttori vuoti.

Quando una classe contiene un distruttore, viene creata una voce nella coda Finalize.

Quando il distruttore viene chiamato, il Garbage Collector viene richiamato per elaborare la coda.

Se il distruttore è vuoto, si verifica semplicemente un calo di prestazioni.

Nota (II) In c# non sono necessarie numerose attività di gestione della memoria

GC gestisce la memoria managed

Se si incapsulano risorse unmanaged (finestre, file, connessioni di rete, …) è necessario utilizzare i distruttori per liberare tali risorse

Quando l'oggetto può essere distrutto, il GC esegue il metodo Finalize dell'oggetto

Dispose Ok, il distruttore serve per rilasciare le risorse unmanaged,

ma se io ho bisogno di un rilascio deterministico di queste risorse?

C’è bisogno di un metodo da poter richiamare manualmente da codice

Questo metodo è il metodo Dispose() dell’interfaccia IDisposable

IDisposable Definisce un metodo per rilasciare risorse allocate

Questa interfaccia viene utilizzata principalmente per rilasciare risorse non gestite

Quando si chiama una classe che implementa l'interfaccia IDisposable, utilizzare il modello try/finally

public interface IDisposable

{

void Dispose();

}

try/finally Quando si utilizzano classi che implementano l’interfaccia IDisposable, bisogna sempre chiamare il metodo Dispose() non appena l’oggetto non è più utile

SqlConnection conn = null;

try{

conn = new SqlConnection(...);

...

}

finally

{

if (conn != null)

{

(conn as IDisposable).Dispose();

}

}

Osservazione Pattern “alloca-utilizza-rilascia”

Definisco il riferimento prima del try

Nel blocco try

Creo l’oggetto

Utilizzo l’oggetto

Nel blocco finally

Controllo che l’oggetto sia diverso da null

Eseguo un cast ad un oggetto IDisposable

Richiamo il metodo Dispose

Funziona alla perfezione ma il codice è molto prolisso

using Lo statement using è stato creato apposta per eseguire il pattern

“alloca-utilizza-rilascia” in modo più conciso

SqlConnection conn = null;

try{

conn = new SqlConnection(...);

...

}

finally{

if (conn != null){

(conn as IDisposable).Dispose();

}

}

using (SqlConnection conn = new SqlConnection (...))

{

...

}

Dietro le quinte il compilatore c# trasforma il codice in alto nel codice a sinistra

Nota All'interno del blocco using, l'oggetto è di sola lettura e non può essere

modificato né riassegnato

Dispose e finalize 4 casi possibili

No Dispose, no Finalize

Si Dispose, no Finalize

Si Dispose, si Finalize

No Dispose, si Finalize

No Dispose, No Finalize Il proprio oggetto

Utilizza la memoria

Utilizza altre risorse che non richiedono la deallocazione esplicita

Rilascia una risorsa unmanaged prima di uscire dal metodo che l’ha allocata

Questo è di gran lunga il caso più frequente

Si Dispose, No Finalize Il proprio oggetto

Alloca indirettamente risorse diverse dalla memoria attraverso altri oggetti .NET

Si vuole fornire ai client un metodo per rilasciare queste risorse prima possibile

È il secondo caso più frequente

class Sample : IDisposable

{

private bool disposed;

void IDisposable.Dispose(){

if (diposed) return;

disposed = true;

// rilascia le risorse

}

public void Method(){

if (disposed) throw new

ObjectDisposedException(...);

// codice del metodo

}

Si Dispose, Si Finalize Il proprio oggetto

Alloca direttamente una risorsa (tipicamente invocando un metodo di una DLL unmanaged) che richiede una deallocazione o rilascio esplicito

Questa deallocazione esplicita si fa nel metodo Finalize, ma si fornisce anche il metodo Dispose, per dare ai client la possibilità di rilasciare la risorsa prima della finalizzazione dei propri oggetti

No Dispose, Si Finalize Non si ha alcuna risorsa da rilasciare, ma si ha necessità di eseguire una

determinata azione quando il proprio oggetto viene finalizzato

È il caso meno probabile e in pratica utile sono in alcuni casi particolari

Dispose e finalize 4 casi possibili

No Dispose, no Finalize

Si Dispose, no Finalize

Si Dispose, si Finalize

No Dispose, si Finalize

Primo approccio (I)public class ClipBoardWrapper : IDisposable

{

[DllImport("user32")]

private static extern int OpenClipboard(int hwnd);

[DllImport("user32")]

private static extern int CloseClipboard();

// Ricorda se la clipboard è correttamente aperta.

private bool isOpen;

// Apre la clipboard e la associa a una finestra.

public void Open(int hWnd)

{

// OpenClipboard restituisce 0 in caso d’errore

if(OpenClipboard(hWnd)

throw new Exception (“Unable to open the clipboard.”);

isOpen = true;

}

// continua...

Primo approccio (II)// Chiude la Clipboard – ignora il comando se non è aperta.

public void Close()

{

if (isOpen) CloseClipboard();

isOpen = false;

}

void IDisposable.Dispose()

{

Close();

}

~ClipBoardWrapper()

{

Close();

}

}

Problema 1 Se il GC chiama il metodo Finalize si ha una perdita di prestazioni

Bisogna fare in modo di richiamare il metodo Finalize solo quando è indispensabile

Non si deve chiamare quando è stato chiamato esplicitamente Dispose

Soluzione

Si richiama nel metodo Dispose GC.SuppressFinalize()

Problema 2 Il codice di rilascio potrebbe accedere ad altri oggetti referenziati

dall’oggetto corrente

Non si deve mai eseguire questo accesso se il codice di rilascio è in esecuzione nella fase di finalizzazione, poiché questi altri oggetti potrebbero essere già stati finalizzati

Soluzione

Si sposta il codice di rilascio effettiva in una versione in overload del metodo Dispose

Questo metodo accetta un argomento bool che specifica se l’oggetto è distrutto o finalizzato ed evita di accedere ad oggetti esterni nel secondo caso

Pattern Dispose-Finalize

Approccio semplificato Si avvolge ciascuna risorsa unmanaged che necessita della

finalizzazione con una classe il cui solo membro è un campo che contiene l’handle della risorsa unmanaged

Si nidifica questa classe wrapper all’interno di un’ulteriore classe che implementa il metodo dispose (ma non il metodo Finalize)

La classe privata viene contrassegnata come private

Demo

Caratteristiche UnmamagedResourceWrapper

Non deve contenere alcun campo eccetto l’handle

Se la risorsa unmanaged deve interagire con altre risorse, il codice deve essere posizionato in WinResource

WinResource

Deve coordinare tutte le risorse (managed e unmanaged) che ha allocato, e deve rilasciarle nel metodo Dispose

Vantaggi (I) Se il codice client omette di invocare il metodo WinResource.Dispose

La memoria utilizzata dall’oggetto WinResource verrà comunque azzerata alla prima garbage collection

L’oggetto interno ha il metodo Finalize e perciò verrà rilasciato solo durante la successiva garbage collection, ma questo oggetto consuma pochissima memoria e perciò non rappresenta un problema serio

UnmamagedResourceWrapper è privato e sealed

Non è necessario scrivere codice complesso che tenga conto delle classi derivate

Il codice all’esterno della classe WinResource non può ottenere un riferimento all’oggetto UnmamagedResourceWrapper e non può farlo risorgere

Vantaggi (II) UnmamagedResourceWrapper ha un solo campo e questo campo è

di tipo value Il codice dei metodi Dispose o Finalize non può accedere erroneamente ad

alcun tipo di reference

Poiché c’è un solo handle di cui tenere conto, non è necessario scrivere del codice che si occupa degli errori nel costruttore

Se il costruttore fallisce il valore del campo handle continua ad essere uguale alla costante InvalidHandle

Il metodo Dispose può rilevare questa condizione e saltare il codice di rilascio

La classe UnmamagedResourceWrapper è così semplice e generica che spesso si può copiare e incollare il relativo codice all’interno di un’altra classe

using e Dispose

top related