Pages

Monday, June 18, 2012

C# Memory Leaks and Marco Polo

 
C# leaks memory.

This is somewhat surprising to those who thought C#'s memory management system eliminated this problem. In fairness, a memory leak in C# is not the same think as a memory leak in C/C++.

In C/C++, when we allocate memory from the heap, we are provided a pointer to the memory.  If we overwrite the pointer or if the pointer goes out of scope, the allocated memory is lost: forever allocated,  forever inaccessible.  As these leaks build up, our program will eventually consume more resources than the operating system can provide, and tragedy results.

In C#, the above scenario is impossible.  If we overwrite a reference to allocated memory, the memory is automatically reclaimed.  Instead, a memory leak in C# occurs when we allocate memory, then store a reference to that allocated memory in a list that we never clear.  At first, this scenario seems to be more the result of belligerent programming.

Yet, there is a construct (called an event) in C# that can amass references to allocated memory without the programmer being aware of the fact.  This then exonerates the intentions of the programmer, and puts the blame back on the language.  However, once the programmer is aware of how events work, then memory leaks in C# can be avoided and the programmer is again held accountable.

The point of this post is to educate the programmer on how easily C# events can accumulate references to memory.


Consider the game of Marco Polo.

class Swimmer
{
    public Swimmer(Stringname)
    {
        _name = name;
        Console.WriteLine("Swimmer " + name + " created");
        Program.MarcoEvent += MarcoHandler;
    }
 
    void MarcoHandler()
    {
        Console.WriteLine("   " + _name + ": Polo");
    }
 
    String _name;
}

Here is a simple class called Swimmer.  It has a name.  It displays the fact that it was constructed on the console.  More interestingly, it registers itself to be notified of an event, called a MarcoEvent.  We will see the code that provides the Program.MarcoEvent later.  In any case, the response of Swimmer to a MarcoEvent is to call the function MarcoHandler to display it's name on the console.

class Game
{
    public void Run()
    {
        while (true)
        {
            var key = Console.ReadKey(true).Key;
 
            if (key == ConsoleKey.Delete)
            {
                _swimmers.Clear();
                Console.WriteLine("CLEARED");
            }
            else
            {
                _swimmers.Add(new Swimmer(key.ToString()));
            }
        }
    }
 
    List<Swimmer> _swimmers = new List<Swimmer>();
}

Here is the user interface to the game.  The user can press a keyboard key, like the letter "A".  In response, this program will create a new Swimmer, give it a name that is the keyboard letter pressed, and add the swimmer to a list of swimmers.  If the user presses the delete key, the list of swimmers is cleared.

delegate void MarcoDelegate();
static class Program
{
    public static event MarcoDelegateMarcoEvent;
 
    static void Main()
    {
        Console.WriteLine("Marco Polo");
        var timer = new Timer(Tick, null, 0, 5000);
        new Game().Run();
    }
 
    static void Tick(object state)
    {
        Console.WriteLine("Marco...");
        if (MarcoEvent != null)
        {
            MarcoEvent();
        }
    }
}

Finally, here is the program class that ties everything together.  A timer is started that raises the MarcoEvent every 5 seconds, and the Game object is created and its Run method executed.


Here is an example of running the above code:
  1. The program starts, displaying "Marco Polo"
  2. After 5 seconds, the Tick event fires, displaying "Marco..." (with no swimmer response)
  3. After another 5 seconds, the Tick event fires, displaying "Marco..." (again, no response)
  4. The user presses A, B and C - creating three Swimmer objects.
  5. The next Tick event fires, raising the MarcoEvent which is heard by all three swimmers.
  6. After another 5 seconds, the user presses the delete key.  The list of swimmers is cleared.
  7. After another 5 seconds, the MarcoEvent is raised again, and surprisingly all three swimmers respond.
The reason the three swimmer objects are still around is because the event MarcoEvent retains a reference to all of them.
 
To avoid this behavior in your code, make sure you understand that events retain references to the objects that register with it.  You have the following options to release these references:
  1. Use the -= operator before deleting a Swimmer:  Program.MarcoEvent -= MarcoHandler
  2. Clear all references to the event handler: Program.MarcoEvent = null
  3. Use a weak event handler.  Many people have written weak event handler classes, and Microsoft's .NET 4.5 contains a WeakEventManager.
In my opinion, none of these are easy nor ideal solutions.  I believe a change to the underlying language is required to properly solve this problem.  But, if you do not solve this problem in your code, your user's may look as confused as these poor passengers who rode the Marco Polo as she crash ashore under full steam.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.