From C# .Net 4.0 Netshell
--Event Handler
--Database Connection
--Timers
Managed Memory Leaks
In unmanaged languages such as C++, you must remember to manually deallocate
memory when an object is no longer required; otherwise, a memory leak will result.
In the managed world, this kind of error is impossible due to the CLR’s automatic
garbage collection system.
Nonetheless, large and complex .NET applications can exhibit a milder form of the
same syndrome with the same end result: the application consumes more and more
memory over its lifetime, until it eventually has to be restarted. The good news is
that managed memory leaks are usually easier to diagnose and prevent.
Managed memory leaks are caused by unused objects remaining alive by virtue of
unused or forgotten references. A common candidate is event handlers—these hold
a reference to the target object (unless the target is a static method). For instance, consider the following classes:
class Host
{
public event EventHandler Click;
}
class Client
{
Host _host;
public Client (Host host)
{
_host = host;
_host.Click += HostClicked;
}
void HostClicked (object sender, EventArgs e) { ... }
}
The following test class contains a method that instantiates 1,000 clients:
class Test
{
static Host _host = new Host();
public static void CreateClients()
{
Client[] clients = Enumerable.Range (0, 1000)
.Select (i => new Client (_host))
.ToArray();
// Do something with clients ...
}
}
You might expect that after CreateClients finishes executing, the 1,000 Client objects
will become eligible for collection. Unfortunately, each client has another referee:
the _host object whose Click event now references each Client instance. This
may go unnoticed if the Click event doesn’t fire—or if the HostClicked method
doesn’t do anything to attract attention.
One way to solve this is to make Client implement IDisposable, and in the
Dispose method, unhook the event handler:
public void Dispose() { _host.Click -= HostClicked; }
Consumers of Client then dispose of the instances when they’re done with them:
Array.ForEach (clients, c => c.Dispose());
--Timer
Timers (System.Timers)
Forgotten timers can also cause memory leaks (we discuss timers in Chapter 21).
There are two distinct scenarios, depending on the kind of timer. Let’s first look at
the timer in the System.Timers namespace. In the following example, the Foo class
(when instantiated) calls the tmr_Elapsed method once every second:
using System.Timers;
class Foo
{
Timer _timer;
Foo()
{
_timer = new System.Timers.Timer { Interval = 1000 };
_timer.Elapsed += tmr_Elapsed;
_timer.Start();
}
The timer in the System.Threading namespace, however, is special. The .NET Framework
doesn’t hold references to active threading timers; it instead references the
callback delegates directly. This means that if you forget to dispose of a threading
timer, a finalizer can (and will) fire—and this will automatically stop and dispose
the timer. This can create a different problem, however, which we can illustrate as
follows:
static void Main()
{
var tmr = new System.Threading.Timer (TimerTick, null, 1000, 1000);
GC.Collect();
System.Threading.Thread.Sleep (10000); // Wait 10 seconds
}
static void TimerTick (object notUsed) { Console.WriteLine ("tick"); }
If this example is compiled in “release” mode (debugging disabled and optimizations
enabled), the timer will be collected and finalized before it has a chance to fire even
once! Again, we can fix this by disposing of the timer when we’re done with it:
using (var tmr = new System.Threading.Timer (TimerTick, null, 1000, 1000))
{
GC.Collect();
System.Threading.Thread.Sleep (10000); // Wait 10 seconds
}
The implicit call to tmr.Dispose at the end of the using block ensures that the tmr
variable is “used” and so not considered dead by the GC until the end of the block.
Ironically, this call to Dispose actually keeps the object alive longer!
void tmr_Elapsed (object sender, ElapsedEventArgs e) { ... }
}
Unfortunately, instances of Foo can never be garbage-collected! The problem is
the .NET Framework itself holds references to active timers so that it can fire their
Elapsed events. Hence:
• The .NET Framework will keep _timer alive.
• _timer will keep the Foo instance alive, via the tmr_Elapsed event handler.
The solution is obvious when you realize that Timer implements IDisposable. Disposing
of the timer stops it and ensures that the .NET Framework no longer references
the object:
class Foo : IDisposable
{
...
public void Dispose() { _timer.Dispose(); }
}