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); } } }