Chapter 12 - Disposal and Garbage Collection
Current Memory Stats
void Main()
{
long totalMemAllocated = 0;
for (int i = 0; i < 200; i++)
{
totalMemAllocated += AllocateSomeNonreferencedMemory();
string procName = Process.GetCurrentProcess().ProcessName;
using PerformanceCounter pcPB = new PerformanceCounter ("Process", "Private Bytes", procName);
long memoryUsed = GC.GetTotalMemory (false); // Change to true to force a collection before reporting used memory
Console.WriteLine ($"Currently OS allocated: {pcPB.NextValue()}. Current GC reported {memoryUsed}. Allocated at some point {totalMemAllocated}.");
}
}
long AllocateSomeNonreferencedMemory()
{
int loops = 64;
int size = 1024;
for (int i = 0; i < loops; i++)
{
int[] array = new int [size];
}
return loops * size * 4; // int is 32-bits (4 bytes)
}
Anonymous Disposal - Problem
void Main()
{
var foo = new Foo();
// Test it without calling SuspendEvents()
foo.FireSomeEvent();
// Now test it with event suspension:
using (foo.SuspendEvents())
{
foo.FireSomeEvent();
}
// Now test it again without calling SuspendEvents()
foo.FireSomeEvent();
}
class Foo
{
int _suspendCount;
public IDisposable SuspendEvents()
{
_suspendCount++;
return new SuspendToken (this);
}
public void FireSomeEvent()
{
if (_suspendCount == 0)
"Event would fire".Dump();
else
"Event suppressed".Dump();
}
class SuspendToken : IDisposable
{
Foo _foo;
public SuspendToken (Foo foo) => _foo = foo;
public void Dispose()
{
if (_foo != null) _foo._suspendCount--;
_foo = null;
}
}
}
Anonymous Disposal - Solution
void Main()
{
var foo = new Foo();
// Test it without calling SuspendEvents()
foo.FireSomeEvent();
// Now test it with event suspension:
using (foo.SuspendEvents())
{
foo.FireSomeEvent();
}
// Now test it again without calling SuspendEvents()
foo.FireSomeEvent();
}
class Foo
{
int _suspendCount;
public IDisposable SuspendEvents()
{
_suspendCount++;
return Disposable.Create (() => _suspendCount--);
}
public void FireSomeEvent()
{
if (_suspendCount == 0)
"Event would fire".Dump();
else
"Event suppressed".Dump();
}
}
// Reusable class
public class Disposable : IDisposable
{
public static Disposable Create (Action onDispose)
=> new Disposable (onDispose);
Action _onDispose;
Disposable (Action onDispose) => _onDispose = onDispose;
public void Dispose()
{
_onDispose?.Invoke();
_onDispose = null;
}
}
Calling Dispose from a finalizer
void Main()
{
}
class Test : IDisposable
{
public void Dispose() // NOT virtual
{
Dispose (true);
GC.SuppressFinalize (this); // Prevent finalizer from running.
}
protected virtual void Dispose (bool disposing)
{
if (disposing)
{
// Call Dispose() on other objects owned by this instance.
// You can reference other finalizable objects here.
// ...
}
// Release unmanaged resources owned by (just) this object.
// ...
}
~Test()
{
Dispose (false);
}
}
Resurrection
string filename = "tempref.tmp";
void Main()
{
// Create and open file so it cannot be deleted
var writer = File.CreateText(filename);
// Get the temporary reference in a separate method.
// Variable will go out of scope upon return and be eligible for GC.
CreateTempFileRef();
GC.Collect(); // Run the garbage collector
TempFileRef._failedDeletions.Dump();
}
void CreateTempFileRef()
{
var tempRef = new TempFileRef(filename);
}
public class TempFileRef
{
static internal ConcurrentQueue<TempFileRef> _failedDeletions
= new ConcurrentQueue<TempFileRef>();
public readonly string FilePath;
public Exception DeletionError { get; private set; }
public TempFileRef (string filePath) { FilePath = filePath; }
// int _deleteAttempt; // Uncomment if re-registering the finalizer
~TempFileRef()
{
try { File.Delete (FilePath); }
catch (Exception ex)
{
DeletionError = ex;
_failedDeletions.Enqueue (this); // Resurrection
// We can re-register for finalization by uncommenting:
// if (_deleteAttempt++ < 3) GC.ReRegisterForFinalize (this);
}
}
}
Array Pooling
int[] pooledArray = ArrayPool<int>.Shared.Rent (100); // 100 bytes
ArrayPool<int>.Shared.Return (pooledArray);
Managed Memory Leaks
void Main()
{
CreateClients();
}
static Host _host = new Host();
public static void CreateClients()
{
Client[] clients = Enumerable.Range (0, 1000)
.Select (i => new Client (_host))
.ToArray();
// Do something with clients ...
}
class Host
{
public event EventHandler Click;
}
class Client
{
Host _host;
public Client (Host host)
{
_host = host;
_host.Click += HostClicked;
}
void HostClicked (object sender, EventArgs e) { }
}
class Test
{
}
Timers - System.Timer
void Main()
{
}
class Foo : IDisposable
{
System.Timers.Timer _timer;
Foo()
{
_timer = new System.Timers.Timer { Interval = 1000 };
_timer.Elapsed += tmr_Elapsed;
_timer.Start();
}
void tmr_Elapsed (object sender, ElapsedEventArgs e) { }
public void Dispose() { _timer.Dispose(); }
}
Timers - Threading timer
static void Main()
{
using (var tmr = new System.Threading.Timer (TimerTick, null, 1000, 1000))
{
GC.Collect();
System.Threading.Thread.Sleep (10000); // Wait 10 seconds
}
}
static void TimerTick (object notUsed) { Console.WriteLine ("tick"); }
Weak References
void Main()
{
AddWidgets(); // In another method so they go out of scope
Console.WriteLine("Before GC:");
Widget.ListAllWidgets();
GC.Collect();
Console.WriteLine ("After GC:");
Widget.ListAllWidgets();
}
void AddWidgets()
{
new Widget ("foo");
new Widget ("bar");
}
class Widget
{
static List<WeakReference> _allWidgets = new List<WeakReference>();
public readonly string Name;
public Widget (string name)
{
Name = name;
_allWidgets.Add (new WeakReference (this));
}
public static void ListAllWidgets()
{
foreach (WeakReference weak in _allWidgets)
{
Widget w = (Widget)weak.Target;
if (w != null) Console.WriteLine (w.Name);
}
}
}
Weak Delegate
void Main()
{
}
public class Foo
{
WeakDelegate<EventHandler> _click = new WeakDelegate<EventHandler>();
public event EventHandler Click
{
add { _click.Combine (value); }
remove { _click.Remove (value); }
}
protected virtual void OnClick (EventArgs e)
=> _click.Target?.Invoke (this, e);
}
public class WeakDelegate<TDelegate> where TDelegate : class
{
class MethodTarget
{
public readonly WeakReference Reference;
public readonly MethodInfo Method;
public MethodTarget (Delegate d)
{
// d.Target will be null for static method targets:
if (d.Target != null) Reference = new WeakReference (d.Target);
Method = d.Method;
}
}
List<MethodTarget> _targets = new List<MethodTarget>();
public WeakDelegate()
{
if (!typeof (TDelegate).IsSubclassOf (typeof (Delegate)))
throw new InvalidOperationException
("TDelegate must be a delegate type");
}
public void Combine (TDelegate target)
{
if (target == null) return;
foreach (Delegate d in (target as Delegate).GetInvocationList())
_targets.Add (new MethodTarget (d));
}
public void Remove (TDelegate target)
{
if (target == null) return;
foreach (Delegate d in (target as Delegate).GetInvocationList())
{
MethodTarget mt = _targets.Find (w =>
Equals (d.Target, w.Reference?.Target) &&
Equals (d.Method.MethodHandle, w.Method.MethodHandle));
if (mt != null) _targets.Remove (mt);
}
}
public TDelegate Target
{
get
{
Delegate combinedTarget = null;
foreach (MethodTarget mt in _targets.ToArray())
{
WeakReference wr = mt.Reference;
// Static target || alive instance target
if (wr == null || wr.Target != null)
{
var newDelegate = Delegate.CreateDelegate (
typeof (TDelegate), wr?.Target, mt.Method);
combinedTarget = Delegate.Combine (combinedTarget, newDelegate);
}
else
_targets.Remove (mt);
}
return combinedTarget as TDelegate;
}
set
{
_targets.Clear();
Combine (value);
}
}
}