Improving .NET Application Performance Part 10: Exception Management

In this article in the optimizing .NET code series, we’ll discuss “Exception Management”. Structured exception handling using try/catch blocks is the recommended way to handle exceptional error conditions in managed code. You should also use finally blocks (or the C# using statement) to ensure that resources are closed even in the event of exceptions.

While exception handling is recommended to make your code more robust, there is a performance cost. Throwing and catching exceptions is expensive and thus you should use exceptions only in circumstances where it is required and never to control regular logic flow. We’ll outline the Exception Handling guidelines in the following sections.

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

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 re-thrown 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 re-throwing exceptions. The following code catches all System.IO exceptions.

catch ( System.IO )

{

  // evaluate the exception

}

Re-throwing Is Expensive

The cost of using throw to re-throw an existing exception is approximately the same as throwing a new exception. In the following code, there is no savings from re-throwing 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.

More Information

You can find more information on exception management using the following links:

·         Chapter 15, “Measuring .NET Application Performance

·         Microsoft Knowledge Base article 315965, “HOW TO: Use Structured Exception Handling in Visual Basic .NET,” at http://support.microsoft.com/default.aspx?scid=kb;en-us;315965.

·         Microsoft Knowledge Base article 308043, “HOW TO: Obtain Underlying Provider Errors by Using ADO.NET in Visual Basic .NET,” at http://support.microsoft.com/default.aspx?scid=kb;en-us;308043.

·         “Exception Handling Application Block” included with the Enterprise Library” on MSDN.

·         “Design Guidelines for Exceptions” on MSDN at http://msdn.microsoft.com/en-us/library/ms229014(VS.80).aspx.

·         “Best Practices for Handling Exceptions” in the .NET Framework Developer’s Guide on MSDN at http://msdn.microsoft.com/en-us/library/seyhszts.aspx.

As you can see, exception handling can have a big impact on the performance of your .NET application. Another topic that affects performance is iteration and looping. We’ll discuss this in our next article.

You might also like