How Do Events Cause Memory Leaks in C# and How Do Weak References Help Mitigate That

How do events cause memory leaks in C# and how do Weak References help mitigate that?

When a listener attaches an event listener to an event, the source object will get a reference to the listener object. This means that the listener cannot be collected by the garbage collector until either the event handler is detached, or the source object is collected.

Consider the following classes:

class Source
{
public event EventHandler SomeEvent;
}

class Listener
{
public Listener(Source source)
{
// attach an event listner; this adds a reference to the
// source_SomeEvent method in this instance to the invocation list
// of SomeEvent in source
source.SomeEvent += new EventHandler(source_SomeEvent);
}

void source_SomeEvent(object sender, EventArgs e)
{
// whatever
}
}

...and then the following code:

Source newSource = new Source();
Listener listener = new Listener(newSource);
listener = null;

Even though we assign null to listener, it will not be eligible for garbage collection, since newSource is still holding a reference to the event handler (Listener.source_SomeEvent). To fix this kind of leak, it is important to always detach event listeners when they are no longer needed.

The above sample is written to focus on the problem with the leak. In order to fix that code, the easiest will perhaps be to let Listener hold on to a reference to Source, so that it can later detach the event listener:

class Listener
{
private Source _source;
public Listener(Source source)
{
_source = source;
// attach an event listner; this adds a reference to the
// source_SomeEvent method in this instance to the invocation list
// of SomeEvent in source
_source.SomeEvent += source_SomeEvent;
}

void source_SomeEvent(object sender, EventArgs e)
{
// whatever
}

public void Close()
{
if (_source != null)
{
// detach event handler
_source.SomeEvent -= source_SomeEvent;
_source = null;
}
}
}

Then the calling code can signal that it is done using the object, which will remove the reference that Source has to ´Listener`;

Source newSource = new Source();
Listener listener = new Listener(newSource);
// use listener
listener.Close();
listener = null;

Is unwired event a memory leak?

No, because when you unwire an event, the delegate (it's an object) which was wired to the event is no longer rooted, and will be collected when the GC sees fit to do so. This is assuming of course the event delegate isn't attached to multiple handlers, in which case it won't be collected until it is unwired from all events.

Why and How to avoid Event Handler memory leaks?

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.

Does this event-handling code cause a memory leak?

No, unless you are in an infinite loop.

Why this does not cause a memory leak when event is not unsubscribed

This is a case of a circular reference. The form has a reference to the object that listens to the event through the John field. In turn, John has a reference to the form when its UponWakingUp event was subscribed by the form's constructor.

Circular references can be a problem in certain automatic memory management schemes, particularly so in reference counting. But the .NET garbage collector doesn't have a problem with. As long as neither the form object nor the Person object have any additional references, the circular reference between the two cannot keep each other alive.

There are no additional references to either in your code. Which would normally cause both objects to be garbage collected. But the Form class is special, as long as a native Windows window for it exists, an internal reference stored in a handle-to-object table maintained by Winforms keeps the form object alive. Which keeps John alive.

So the normal way this is cleaned-up is that the user closes the window by clicking the X in the upper right corner. Which in turn causes the native window handle to be destroyed. Which removes the form reference from that internal table. The next garbage collection now sees nothing but the circular reference and collects them both.

C# WeakReference use

Classically WeakReferencing is used for relatively large memory objects which are not time consuming to create. By weak-referencing such objects we let GC know that it is okay to wipe out despite the active reference; if the referenced object is called again then it is created again. Here is the blog link . The benefit is to avoid keeping your memory occupied and putting it at use for other stuff and when the weak reference is used again then it is created again (which presumably has to be fast).

This stackoverflow post explains why it isn't going to cause memory leaks (even if there is a possible circular reference).

Following is the code snippet which uses your parent and child classes and creates a huge demonstrator object then later sets it to null. The process is repeated three times and memory is measured at different stages. After initial memory bump, all other pre-creation post-creation and post-null readings don't change which means no memory leaking!.

    class Program
{
static void Main(string[] args)
{
MonitorMemoryUsage();
MonitorMemoryUsage();
MonitorMemoryUsage();

Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}

private static void MonitorMemoryUsage()
{
DisplayMemoryAfterGc("Before creation");
var demonstrator = new Demonstrator();

DisplayMemoryAfterGc("After creation");

if (demonstrator.Children != null && demonstrator.Children.Count > 0)
{
demonstrator.Children = null;
demonstrator.Parents = null;
demonstrator = null;
}

Console.WriteLine(demonstrator == null);
DisplayMemoryAfterGc("After null");
}

private static void DisplayMemoryAfterGc(string eventType)
{
GC.Collect();
GC.WaitForFullGCComplete();
var totalMemory = GC.GetTotalMemory(true);
Console.WriteLine(eventType + ":" + totalMemory);
}
}

public class Demonstrator
{
public List<Parent> Parents { get; set; }
public List<Child> Children { get; set; }

public Demonstrator()
{
Parents = new List<Parent>();
Children = new List<Child>();

for (int i = 0; i < 10000; i++)
{
var parent = new Parent();

Parents.Add(parent);

Children.Add(parent._child1);
}
}

}

public class Parent
{

public Child _child1, _child2;

void someFunc()
{
_child1 = new Child();
_child2 = new Child();
_child1.handle += parentHandle;
}

void parentHandle(Child sender)
{
Console.WriteLine("Sender is: " + sender.ToString());
}
}

public class Child
{

public OnHandle handle;

public delegate void OnHandle(Child sender);

void someChildFunc()
{
handle(this);
}

}

Memory leaks in .NET

Block the finalizer thread. No other objects will be garbage collected until the finalizer thread is unblocked. Thus the amount of memory used will grow and grow.

Further reading: http://dotnetdebug.net/2005/06/22/blocked-finalizer-thread/

Controls events causes memory leaks?

No, when the page is out of scope, the CLR will mark it, including its event handlers, as ready for garbage collection. You do not need to unregister event handlers in your pages.

The situation is different when the event handlers are in other objects than the object that raises the event. In the example that you give, that is not the case.

If the object that subscribes to another object's event goes out of scope first, it must unsubscribe from the event handlers or it will actually only be marked for garbage collection when the event source also goes out of scope.

If the object that raises the event goes out of scope first, there is no problem because the subscriptions will also go out of scope.



Related Topics



Leave a reply



Submit