Avoiding memory leaks |
When using Win2D controls in managed XAML applications, care must be taken to avoid reference count cycles that could prevent these controls ever being reclaimed by the garbage collector.
If all these conditions are met, a reference count cycle will keep the Win2D control from ever being garbage collected. New Win2D resources are allocated each time the app moves to a different page, but the old ones are never freed so memory is leaked. To avoid this, you must add code to explicitly break the cycle.
To break the reference count cycle and let your page be garbage collected:
Example code:
void page_Unloaded(object sender, RoutedEventArgs e) { this.canvas.RemoveFromVisualTree(); this.canvas = null; }
For working examples, see any of the Example Gallery demo pages.
To test whether your application is correctly breaking refcount cycles, add a finalizer method to any XAML pages which contain Win2D controls:
~MyPage()
{
System.Diagnostics.Debug.WriteLine("~" + GetType().Name);
}
In your App constructor, set up a timer that will make sure garbage collection occurs at regular intervals:
var gcTimer = new DispatcherTimer(); gcTimer.Tick += (sender, e) => { GC.Collect(); }; gcTimer.Interval = TimeSpan.FromSeconds(1); gcTimer.Start();
Navigate to the page, then away from it to some other page. If all cycles have been broken, you will see Debug.WriteLine output in the Visual Studio output pane within a second or two.
Note that calling GC.Collect is disruptive and hurts performace, so you should remove this test code as soon as you finish testing for leaks!
A cycle occurs when an object A has a reference to B, at the same time as B also has a reference to A. Or when A references B, and B references C, while C references A, etc.
When subscribing to events of a XAML control, this sort of cycle is pretty much inevitable:
If all the objects involved are implemented in .NET, such cycles are not a problem because .NET is garbage collected, and the garbage collection algorithm is able to identify and reclaim groups of objects even if they are linked in a cycle.
Unlike .NET, C++ manages memory by reference counting, which is unable to detect and reclaim cycles of objects. In spite of this limitation, C++ apps using Win2D have no problem because C++ event handlers default to holding weak rather than strong references to their target instance. Therefore the page references the control, and the control references the event handler delegate, but this delegate does not reference back to the page so there is no cycle.
The problem comes when a C++ WinRT component such as Win2D is used by a .NET application:
A cycle is present, but the Win2D objects participating in this cycle are not using .NET garbage collection. This means the garbage collector is unable to see the entire chain, so it cannot detect or reclaim the objects. When this occurs, the application must help out by explicitly breaking the cycle. This can be done either by releasing all references from the page to the control (as recommended above) or by releasing all references from the control to event handler delegates that might point back to the page (using the page Unloaded event to unsubscribe all event handlers).