.NET 2.0 Performance Guidelines - Exception Management

From Guidance Share
Jump to navigationJump to search

- J.D. Meier, Srinath Vasireddy, Ashish Babbar, Rico Mariani, and Alex Mackman


Do Not Use Exceptions to Control Application Flow

Throwing exceptions is expensive. Do not use exceptions to control application flow. If you can reasonably expect a sequence of events to happen in the normal course of running code, you probably should not throw any exceptions in that scenario.

The following code throws an exception inappropriately, when a supplied product is not found.

static void ProductExists( string ProductId)
{
 //... search for Product
 if ( dr.Read(ProductId) ==0 ) // no record found, ask to create
 {
   throw( new Exception("Product Not found"));
 }
}

Because not finding a product is an expected condition, refactor the code to return a value that indicates the result of the method's execution. The following code uses a return value to indicate whether the customer account was found.

static bool ProductExists( string ProductId)
{
 //... search for Product
 if ( dr.Read(ProductId) ==0 ) // no record found, ask to create
 {
   return false;
 }
 . . .
}

Returning error information using an enumerated type instead of throwing an exception is another commonly used programming technique in performance-critical code paths and methods.


Use Validation Code to Reduce Unnecessary Exceptions

If you know that a specific avoidable condition can happen, proactively write code to avoid it. For example, adding validation checks such as checking for null before using an item from the cache can significantly increase performance by avoiding exceptions. The following code uses a try/catch block to handle divide by zero.

double result = 0;
try{
 result = numerator/divisor;
}
catch( System.Exception e){
 result = System.Double.NaN;
}

The following rewritten code avoids the exception, and as a result is more efficient.

double result = 0;
if ( divisor != 0 )
 result = numerator/divisor;
else
 result = System.Double.NaN;


Use the finally Block to Ensure Resources Are Released

For both correctness and performance reasons, it is good practice to make sure all expensive resources are released in a suitable finally block. The reason this is a performance issue as well as a correctness issue is that timely release of expensive resources is often critical to meeting your performance objectives.

The following code ensures that the connection is always closed.

SqlConnection conn = new SqlConnection("...");
try
{
 conn.Open();
 //.Do some operation that might cause an exception
 // Calling Close as early as possible
 conn.Close();
 // ... other potentially long operations
}
finally
{
 if (conn.State==ConnectionState.Open)
   conn.Close();  // ensure that the connection is closed
}

Notice that Close is called inside the try block and in the finally block. Calling Close twice does not cause an exception. Calling Close inside the try block allows the connection to be released quickly so that the underlying resources can be reused. The finally block ensures that the connection closes if an exception is thrown and the try block fails to complete. The duplicated call to Close is a good idea if there is other significant work in the try block, as in this example.


Replace Visual Basic .NET On Error Goto Code with Exception Handling

Replace code that uses the Visual Basic .NET On Error/Goto error handling mechanism with exception handling code that uses Try/Catch blocks. On Error Goto code works but Try/Catch blocks are more efficient, and it avoids the creation of the error object.


Do Not Catch Exceptions That You Cannot Handle

Do not catch exceptions unless you specifically want to record and log the exception details or can retry a failed operation. Do not arbitrarily catch exceptions unless you can add some value. You should let the exception propagate up the call stack to a handler that can perform some appropriate processing.

You should not catch generic exceptions in your code as follows.

catch (Exception e)
{….}

This results in catching all exceptions. Most of these exceptions are rethrown eventually. Catching generic exceptions in your code makes it harder to debug the original source of the exception because the contents of the call stack (such as local variables) are gone.

Explicitly name the exceptions that your code can handle. This allows you to avoid catching and rethrowing exceptions. The following code catches all System.IO exceptions.

catch ( System.IO )
{
  // evaluate the exception
}


Be Aware That Rethrowing Is Expensive

The cost of using throw to rethrow an existing exception is approximately the same as throwing a new exception. In the following code, there is no savings from rethrowing the existing exception.

try 
{
   // do something that may throw an exception
  …
}
catch (Exception e)
{
   // do something with e
   throw;
}

You should consider wrapping exceptions and rethrowing them only when you want to provide additional diagnostic information.


Preserve as Much Diagnostic Information as Possible in Your Exception Handlers

Do not catch exceptions that you do not know how to handle and then fail to propagate the exception. By doing so, you can easily obscure useful diagnostic information as shown in the following example.

try
{
 // exception generating code
}
catch(Exception e)
{ 
 // Do nothing
}

This might result in obscuring information that can be useful for diagnosing the erroneous code.


Use Performance Monitor to Monitor CLR Exceptions

Use Performance Monitor to identify the exception behavior of your application. Evaluate the following counters for the .NET CLR Exceptions object:

  • # of Exceps Thrown. This counter provides the total number of exceptions thrown.
  • # of Exceps Thrown / sec. This counter provides the frequency of exceptions thrown.
  • # of Finallys / sec. This counter provides the frequency of finally blocks being executed.
  • Throw to Catch Depth / sec. This counter provides the number of stack frames that were traversed from the frame throwing the exception, to the frame handling the exception in the last second.

Identify areas of your application that throw exceptions and look for ways to reduce the number of exceptions to increase your application's performance.