Consider a class that implements IDisposable
, and that has members in such a way that it will never become eligible for garbage collection when it is not disposed. And as it will not be garbage collected, it will not have the chance to use the destructor for cleaning up.
As a result, when it is not disposed (e.g. unreliable users, programming errors), resources will be leaked.
Is there a general approach how such a class can be designed to deal with such a situation or to avoid it?
Example:
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");
}
}
(Omitted IDisposable
to keep example short.) This class uses a Timer
to do something useful at certain intervals. It needs a reference to the Timer
to avoid it is garbage collected.
Let’s assume the user of that class will not dispose it. As a result of the timer, somewhere some worker thread has a reference to the Cyclical
instance via the callback, and as a result, the Cyclical
instance will never become eligible for garbage collection, and its destructor will never run, and resources will leak.
In this example, a possible fix (or workaround) could be to use a helper class that receives callbacks from the Timer
, and that does not have a reference, but only a WeakReference
to the Cyclical
instance, which it calls using that WeakReference
.
However, in general, is there a design rule for classes like this that need to be disposed to avoid leaking resources?
For the sake of completeness, here the example including IDispose
and including a workaround/solution (and with a hopefully less distracting name):
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);
}
}
- If disposed, the timer will be disposed.
- If not disposed, the object will become eligible for garbage collection.
using
? It's guaranteed to callDispose
for implementations ofIDisposable
on that instance. See here.using
.Cynical
asCynicalStream
and putting in aClose
method and callDispose
yourself withinClose
.AppDomain
teardown might be later than you want, but it does happen...