⬆️ ⬇️

Get rid of memory leaks in WPF

image At DevExpress, we spend a lot of effort on business components for WPF and Silverlight . We have our own line of controls, in the list of which DXPivotGrid has recently entered - replacing Excel's PivotTable tool. In the process of developing new components, we try to maximize the use of existing code. For example, the base classes from the version of PivotGrid for WinForms . Often this creates problems that you have not encountered while developing for .NET 2.0. When I wrote PivotGrid for WPF, I had to solve problems with memory leaks due to a subscription (more precisely, “non-correspondence”) to events.





Microsoft introduced the concept of a weak event in .NET 3.0: this is a variation of standard events that do not hold a direct link to the event handler. On the other hand, ordinary handlers are two links: one to the object, and the second to the method inside the object. Nothing new, but there is a nuance.



The event handler will not be processed by the garbage collector until it is unsubscribed from the event. Considering that the IDisposable interface is not used in WPF, this turns into a big problem.

')

As a solution, Microsoft offers weakly-connected event handlers (weak events - “weak event handlers” in the Microsoft translation ). The garbage collector can process objects that subscribe to such events, even if the subscription still exists.



There are two ways to make a weak event: use the WeakEventManager or RoutedEvent.



WeakEventManager



The WeakEventManager class turns an existing event into a weak event. In my project, it was necessary to subscribe to events from the core of the product, which should be compatible with .NET 2.0.



Weak events are created using two classes: WeakEventManager (dispatcher) and WeakEventListener (listener). The event dispatcher subscribes to it and transfers calls to listeners, i.e. is a layer between the source and the recipient, breaking the rigid connection.



This is the event manager template.



public class MyEventManager : WeakEventManager { static MyEventManager CurrentManager { get { //     Type managerType = typeof(MyEventManager); MyEventManager currentManager = (MyEventManager)WeakEventManager.GetCurrentManager(managerType); if(currentManager == null) { currentManager = new MyEventManager(); WeakEventManager.SetCurrentManager(managerType, currentManager); } return currentManager; } } MyEventManager() { } //  "T"      public static void AddListener(T source, IWeakEventListener listener) { CurrentManager.ProtectedAddListener(source, listener); } //  "T"      public static void RemoveListener(T source, IWeakEventListener listener) { CurrentManager.ProtectedRemoveListener(source, listener); } protected override void StartListening(object source) { //    // , ((T)source).Event += EventHandler; } protected override void StopListening(object source) { //    // , ((T)source).Event -= EventHandler; } //   –    //      void EventHandler(object sender, EventArgs e) { base.DeliverEvent(sender, e); } } 




And this is a snippet with a template for Visual Studio: https://gist.github.com/777559 . Hangs up on the team "wem".



This template can be used for any weak event manager. You must change the source class name, the subscription method, and adjust the type of parameters in the EventHandler method.



Every object that wants to subscribe to a weak event must implement the IWeakEventListener interface. This interface contains the only ReceiveWeakEvent method. You must check the type of event dispatcher, call the handler, and return true. If you cannot determine the type of dispatcher, you must return false. In this case, a System.ExecutionEngineException exception will be thrown with a somewhat incomprehensible text of the cause of the error. According to it, it becomes clear that there is an error in dispatchers or listeners.



Here is the IWeakEventListener interface implementation pattern:



 class MyEventListener : IWeakEventListener { bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e) { if(managerType == typeof(MyEventManager)) { //   return true; // ,     } return false; // -    } } 




Advantages and disadvantages






Routed Events



Forwarded events are the infrastructure for events processed in a XAML (for example, EventTrigger ) or passing through a visual tree.



Their main advantages are:






MSDN has a good article about them: Routed Events Overview and so I don’t want to repeat them here. But I will mention two of their main disadvantages.



Heavy call and no information on the number of subscribers


This is part of the UIElement.RaiseEventImpl method that raises the routed event:



 internal static void RaiseEventImpl(DependencyObject sender, RoutedEventArgs args) { EventRoute route = EventRouteFactory.FetchObject(args.RoutedEvent); if (TraceRoutedEvent.IsEnabled) { TraceRoutedEvent.Trace(TraceEventType.Start, TraceRoutedEvent.RaiseEvent, new object[] { args.RoutedEvent, sender, args, args.Handled }); } try { args.Source = sender; BuildRouteHelper(sender, route, args); route.InvokeHandlers(sender, args); args.Source = args.OriginalSource; } finally { if (TraceRoutedEvent.IsEnabled) { TraceRoutedEvent.Trace(TraceEventType.Stop, TraceRoutedEvent.RaiseEvent, new object[] { args.RoutedEvent, sender, args, args.Handled }); } } EventRouteFactory.RecycleObject(route); } 




It looks fine until you look inside the BuildRouteHelper and InvokeHandlers methods , each one longer than 100 lines. And all this complexity to call a single event. This complexity makes this approach inapplicable to frequently triggered events.



They can only be added to the heirs of the UIElement class.


You cannot create a redirected event for a simple date object that is not inheritable from a UIElement. If your technical requirements do not allow this, then this will be a problem for you.



Eventually



If you are not limited to the old code and you have numerous calls for events, use RoutedEvents . If there are a lot of calls or you have common code with .NET 2.0, you will have to write a WeakEventManager for each. Bulky, but have to.



Both of these methods will work in MediumTrust. If this requirement is not important to you, then wait for the solution number 3 in the next series.

Source: https://habr.com/ru/post/111729/



All Articles