ASP.Net Best Practice – Close, Finalize and Dispose
Many developers are not entirely sure about Garbage Collection and think "should I really worry about garbage collection and what it does behind scenes?". The short answer is Yes - you should worry about garbage collection. Not necessarily because you need to handle it within your application, but because you should have an understanding or how garbage collection and disposal works so your code allows it to be managed properly. Many times the way you write your code affects garbage collection in a massive way. Sometimes this impact leads to reduced and unreliable performance for your application.
When your application starts, server memory is allocated to the application. When your application creates objects, they are assigned in the memory stack with a memory address (just like allocating space to a book on a bookshelf). Internally, garbage collection maintains an object graph (a map of the bookshelf) so it know which objects are reachable.
Let's use an example where there are three objects - A, B and C. These objects were created in that order, so Object A is the oldest and Object C is the youngest. Object A is removed from memory (disposed) and the garbage collector shuffles things around so that memory is optimised. The memory that was allocated to Object A is now assigned to Object B. Object B memory is assigned to Object C and the memory that was allocated to Object C initially is freed and returned to the server for allocation to other processes. As the address pointers are updated, the garbage collector needs to ensure that the internal graph (map of the bookshelf) is updated with all the new memory addresses. That is quite a bit of work for the garbage collector to do, so we want to make the job as easy as possible so our application runs efficiently.
Here is a really good quote relating to object disposal from CodeProject
GC uses the concept of generations to improve performance. Concept of generation is based on the way human psychology handles tasks. Below are some points related to how tasks are handled by humans and how garbage collector algorithm works on the same lines:-
- If you decide some task today there is a high possibility of completion of those tasks.
- If some task is pending from yesterday then probably that task has gained a low priority and it can be delayed further.
- If some task is pending from day before yesterday then there is a huge probability that the task can be pending forever.
GC thinks in the same lines and has the below assumptions:-
- If the object is new then the life time of the object can be short.
- If an object is old then it can have a long life time.
You can see that handling (or not handling) disposal can have significant impact on the performance of your application. If an object is not disposed correctly, the garbage collector thinks it is a long-life object and will persist it's memory allocation. In simple terms, this means your application will have a memory leak - it continues to draw server memory until there is no memory available. At which point either your application will crash or the entire server will crash (this can happen if limits are not configured on the server correctly).
So, how can we code better to avoid garbage collection issues within our application?
Handle Connections
Best practice with database connections is to open as late as possible and close as soon as possible. I won't go into details on how this code looks, because the next point makes it redundant.
Use the "using" Statement
As a rule, when you use an IDisposable object, you should dispose of it when you have finished with it. You could do this manually by calling the Dispose method on the object.
try
{
SqlConnection conn = new SqlConnection(connString);
conn.Open();
SqlCommand cmd = new SqlCommand();
cmd.Connection = conn;
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.CommandText = "proc_StoredProcedure";
cmd.Parameters.Clear();
cmd.Parameters.AddWithValue("@ID", ID);
SqlDataReader dr = cmd.ExecuteReader();
while (dr.Read())
{
// Do something here
}
}
catch (Exception ex)
{
throw new Exception (ex.Message);
}
finally
{
// manually call dispose
((IDisposable)conn).Dispose();
((IDisposable)cmd).Dispose();
}
Or you could use the using statement to do the heavy lifting for you. The using statement calls the Dispose method on the object in the correct way and it also causes the object to go out of scope as soon as Dispose is called.
using (SqlConnection conn = new SqlConnection(connString))
{
try
{
conn.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.CommandText = "proc_StoredProcedure";
cmd.Parameters.Clear();
cmd.Parameters.AddWithValue("@ID", ID);
SqlDataReader dr = cmd.ExecuteReader();
while (dr.Read())
{
// Do something here
}
}
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
In this example, the using statement wraps the try catch block and ensures correct disposal of the objects used within. Notice there is no need to manually call the Dispose() method? That makes things a lot easy for us as developers. There is less code too, and you know how I feel about that (just read some of my other posts and you will realise that I like to keep code short and concise).
Til next time...