Considera una classe che implementa IDisposable
e che ha membri in modo tale da non diventare mai idonea per la garbage collection quando non viene eliminata. E poiché non sarà raccolto, non avrà la possibilità di utilizzare il distruttore per la pulizia.
Di conseguenza, quando non viene smaltito (ad esempio utenti non affidabili, errori di programmazione), le risorse verranno divulgate.
Esiste un approccio generale su come una tale classe può essere progettata per affrontare una situazione del genere o per evitarla?
Esempio :
using System;
class Program
{
static void Main(string[] args)
{
new Cyclical();
GC.Collect();
GC.WaitForPendingFinalizers();
Console.ReadKey();
}
}
class Cyclical
{
public Cyclical()
{
timer = new System.Threading.Timer(Callback, null, 0, 1000);
}
System.Threading.Timer timer;
void Callback(object state)
{
Console.Write('.'); // do something useful
}
~Cyclical()
{
Console.WriteLine("destructor");
}
}
(Ometto IDisposable
per mantenere un esempio breve.) Questa classe usa un Timer
per fare qualcosa di utile a determinati intervalli. Ha bisogno di un riferimento al Timer
per evitare che venga raccolto dei rifiuti.
Supponiamo che l'utente di quella classe non lo disponga. Come risultato del timer, da qualche parte un thread worker ha un riferimento all'istanza Cyclical
tramite il callback e, di conseguenza, l'istanza Cyclical
non diventerà mai idonea per la garbage collection e il suo distruttore non verrà mai eseguito e le risorse perderanno.
In questo esempio, una possibile correzione (o soluzione alternativa) potrebbe essere quella di utilizzare una classe helper che riceva callback da Timer
, e che non ha un riferimento, ma solo un WeakReference
all'istanza Cyclical
, che chiama usando quel WeakReference
.
Tuttavia, in generale, esiste una regola di progettazione per classi come questa che devono essere eliminate per evitare perdite di risorse?
Per completezza, qui l'esempio include IDispose
e include un workaround / solution (e con un nome che spera meno distraente):
class SomethingWithTimer : IDisposable
{
public SomethingWithTimer()
{
timer = new System.Threading.Timer(StaticCallback,
new WeakReference<SomethingWithTimer>(this), 0, 1000);
}
System.Threading.Timer timer;
static void StaticCallback(object state)
{
WeakReference<SomethingWithTimer> instanceRef
= (WeakReference<SomethingWithTimer>) state;
SomethingWithTimer instance;
if (instanceRef.TryGetTarget(out instance))
instance.Callback(null);
}
void Callback(object state)
{
Console.Write('.'); // do something useful
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
Console.WriteLine("dispose");
timer.Dispose();
}
}
~SomethingWithTimer()
{
Console.WriteLine("destructor");
Dispose(false);
}
}
- Se disposto, il timer verrà eliminato.
- Se non disposto, l'oggetto diventerà idoneo per la garbage collection.