alessandro andreose’ - helpdesk website studio e... · il metodo finalize (distruttore) metodo...
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