Why and How to avoid Event Handler memory leaks?

前端 未结 4 1604
爱一瞬间的悲伤
爱一瞬间的悲伤 2020-11-22 16:10

I just came to realize, by reading some questions and answers on StackOverflow, that adding event handlers using += in C# (or i guess, other .net languages) can

相关标签:
4条回答
  • 2020-11-22 16:46

    The cause is simple to explain: while an event handler is subscribed, the publisher of the event holds a reference to the subscriber via the event handler delegate (assuming the delegate is an instance method).

    If the publisher lives longer than the subscriber, then it will keep the subscriber alive even when there are no other references to the subscriber.

    If you unsubscribe from the event with an equal handler, then yes, that will remove the handler and the possible leak. However, in my experience this is rarely actually a problem - because typically I find that the publisher and subscriber have roughly equal lifetimes anyway.

    It is a possible cause... but in my experience it's rather over-hyped. Your mileage may vary, of course... you just need to be careful.

    0 讨论(0)
  • 2020-11-22 16:46

    An event is really a linked list of event handlers

    When you do += new EventHandler on the event it doesn’t really matter if this particular function has been added as a listener before, it will get added once per +=.

    When the event is raised it go through the linked list, item by item and call all the methods (event handlers) added to this list, this is why the event handlers are still called even when the pages are no longer running as long as they are alive (rooted), and they will be alive as long as they are hooked up. So they will get called until the eventhandler is unhooked with a -= new EventHandler.

    See Here

    and MSDN HERE

    0 讨论(0)
  • 2020-11-22 16:49

    I have explained this confusion in a blog at https://www.spicelogic.com/Blog/net-event-handler-memory-leak-16. I will try to summarize it here so that you can have a clear idea.

    Reference means, "Need":

    First of all, you need to understand that, if object A holds a reference to object B, then, it will mean, object A needs object B to function, right? So, the garbage collector won't collect the object B as long as the object A is alive in the memory.

    I think this part should be obvious to a developer.

    += Means, injecting reference of Right side object to the left object:

    But, the confusion comes from the C# += operator. This operator does not clearly tell the developer that, the right-hand side of this operator is actually injecting a reference to the left-hand side object.

    And by doing so, the object A thinks, it needs the object B, even though, from your perspective, the object A should not care if the object B lives or not. As the object A thinks object B is needed, object A protects object B from the garbage collector as long as object A is alive. But, if you did not want that protection given to the event subscriber object, then, you can say, a memory leak occurred.

    You can avoid such a leak by detaching the event handler.

    How to make a decision?

    But, there are lots of events and event handlers in your whole code-base. Does it mean, you need to keep detaching event handlers everywhere? The answer is No. If you had to do so, your codebase will be really ugly with verbose.

    You can rather follow a simple flow chart to determine if a detaching event handler is necessary or not.

    Most of the time, you may find the event subscriber object is as important as the event publisher object and both are supposed to be living at the same time.

    Example of a scenario where you do not need to worry

    For example, a button click event of a window.

    Here, the event publisher is the Button, and the event subscriber is the MainWindow. Applying that flow chart, ask a question, does the Main Window (event subscriber) supposed to be dead before the Button (event publisher)? Obviously No. Right? That won't even make sense. Then, why worry about detaching the click event handler?

    An example when an event handler detachment is a MUST.

    I will provide one example where the subscriber object is supposed to be dead before the publisher object. Say, your MainWindow publishes an event named "SomethingHappened" and you show a child window from the main window by a button click. The child window subscribes to that event of the main window.

    And, the child window subscribes to an event of the Main Window.

    From this code, we can clearly understand that there is a button in the Main Window. Clicking that button shows a Child Window. The child window listens to an event from the main window. After doing something, the user closes the child window.

    Now, according to the flow chart I provided if you ask a question "Does the child window (event subscriber) supposed to be dead before the event publisher (main window)? The answer should be YES. Right? So, detach the event handler. I usually do that from the Unloaded event of the Window.

    A rule of thumb: If your view (i.e. WPF, WinForm, UWP, Xamarin Form, etc.) subscribes to an event of a ViewModel, always remember to detach the event handler. Because a ViewModel is usually lives longer than a view. So, if the ViewModel is not destroyed, any view that subscribed event of that ViewModel will stay in memory, which is not good.

    Proof of the concept using a memory profiler.

    It won't be much fun if we cannot validate the concept with a memory profiler. I have used JetBrain dotMemory profiler in this experiment.

    First, I have run the MainWindow, which shows up like this:

    Then, I took a memory snapshot. Then I clicked the button 3 times. Three child windows showed up. I have closed all of those child windows and clicked the Force GC button in the dotMemory profiler to ensure that the Garbage Collector is called. Then, I took another memory snapshot and compared it. Behold! our fear was true. The Child Window was not collected by the Garbage collector even after they were closed. Not only that, but the leaked object count for the ChildWindow object is also shown "3" (I clicked the button 3 times to show 3 child windows).

    Ok, then, I detached the event handler as shown below.

    Then, I have performed the same steps and checked the memory profiler. This time, wow! no more memory leak.

    0 讨论(0)
  • 2020-11-22 16:50

    Yes, -= is enough, However, it could be quite hard to keep track of every event assigned, ever. (for detail, see Jon's post). Concerning design pattern, have a look at the weak event pattern.

    0 讨论(0)
提交回复
热议问题