Response.Redirect and the ThreadAbortException, and why it’s a good thing
Posted April 5, 2008
Reading time: 5 minutes
A couple of months ago, I ran into a problem where I was seeing a bunch of ThreadAbortExceptions
showing up in my logs. It didn’t take long to track down why – I was calling Response.Redirect
from within a try/catch
block in my ASP.NET code-behind page, and the catch block was catching the ThreadAbortException
and writing it out to the log. But why was the ThreadAbortException
being generated?
It turns out that Response.Redirect
, when called with just the URL, or with the URL and a Boolean value of true, calls the Response.End
method. Response.End
then calls Thread.Abort
on the current thread, and, assuming that the page is in a cancellable state, this method throws the ThreadAbortException
, alerting the framework that it is time to stop this thread, and NOW.
Why is this a good thing?
Let’s step back a bit and look at the recommended workaround for dealing with the ThreadAbortException
. Googling for an answer yields many suggestions to call Response.Redirect
and pass the Boolean
value false
as the second, optional parameter. This effectively suppresses the ThreadAbortException
, preventing it from being caught and potentially logged as an error.
All is well and good, right? Isn’t this what we want?
Not quite.
One side-effect of suppressing the ThreadAbortException
is that any code after Response.Redirect
is still executed. This includes any event handlers in the page lifecycle that have yet to execute. This is a waste of system resources, particularly if any of these event handlers contains resource-intensive code.
For the sake of illustration, let’s assume that our page, Default.aspx, has event handlers for most of the Page’s events:
- Page_PreInit
- Page_Init
- Page_InitComplete
- Page_PreLoad
- Page_Load
- Page_LoadComplete
- Page_PreRender
- Page_PreRenderComplete
- Page_SaveStateComplete
Let’s further assume that each of these event handlers contains a resource- and time-intensive operation. It could be a database call, a network call, or a batch image manipulation. Whatever it is, it requires a lot of resources and a lot of time.
Each event handler will append a string to a StringBuilder
member variable called _EventsFired
. Immediately preceding the call to Response.Redirect
, _EventsFired
will be stored as a Session variable so that the error page, Error.aspx, can display which methods were called. The Redirect
will occur in the Page_Load
method; the user will be sent from Default.aspx to Error.aspx, which will then display the contents of _EventsFired
.
Here is the code listing of Default.aspx’s Page_Load event handler:
|
|
Notice how we are passing false
as the second parameter of Response.Redirect
, meaning that we will prevent a ThreadAbortException
from being thrown.
Here is the output from Error.aspx:
Page_PreInit called. Resource-intensive operation executed.
Page_Init called. Resource-intensive operation executed.
Page_InitComplete called. Resource-intensive operation executed.
Page_PreLoad called. Resource-intensive operation executed.
Page_Load called. Resource-intensive operation executed.
Page_LoadComplete called. Resource-intensive operation executed.
Page_PreRender called. Resource-intensive operation executed.
Page_PreRenderComplete called. Resource-intensive operation executed.
Page_SaveStateComplete called. Resource-intensive operation executed.
Render called.
Page execution did not stop at Page_Load
, even though we told the Application
to complete the request! In all likelihood, this is not the desired behavior, especially if any of the post-Load
event handlers contains time-consuming or resource-intensive operations.
Now let’s modify Default.aspx’s Page_Load
event handler to call Response.Redirect
without suppressing a ThreadAbortException
from being thrown:
|
|
Here is the output from Error.aspx (remember, this is the list of events that fired during the processing of Default.aspx):
|
|
It’s almost what we want, but notice that we “logged” the ThreadAbortException
. We need a way to exclude that from being logged.
Let’s modify Default.aspx’s Page_Load
event handler one more time:
|
|
Here is the output from Error.aspx:
|
|
Success! Finally, the desired result!
- The page stopped processing when it was supposed to, right after the call to
Response.Redirect
. - Event handlers later on in the page lifecycle were not unnecessarily executed.
- The
ThreadAbortException
was not “logged” by our catch block. Instead, it was explicitly caught and rethrown up the stack.
So there we have it. Suppressing the Response.Redirect
-generated ThreadAbortException
can be a bad thing, particularly if there is a lot of resource-intensive code left to execute after the redirect. To prevent those pesky ThreadAbortException
s from showing up in our log files, all we have to do is explicitly catch them and rethrow them. On busy sites, this can help to conserve scarce resources.
Used properly, ThreadAbortException
is your friend.
Update 2007-08-01 07:44
Of course, I could have moved the Response.Redirect
outside of the try/catch.
However, the code that prompted the writing of this article is legacy production code that can’t be modified at my whim, hence the need for this approach.
Update 2008-04-05: Moved over from jonsagara.com.