Tuesday, February 23, 2010

Response.Redirect(url) ThreadAbortException Solution

Response.Redirect(url) ThreadAbortException Solution

by John S. Reid

Since the advent of Server.Transfer(url) I've used Response.Redirect(url) less and less, but for those times when you want the client to know of the location from where information will be retrieved it's still your best option. So you use it, never expecting that the simple call you've used for years with ASP would throw an exception in ASP.NET.

The ThreadAbortException is thrown when you make a call to Response.Redirect(url) because the system aborts processing of the current web page thread after it sends the redirect to the response stream. Response.Redirect(url) actually makes a call to Response.End() internally, and it's Response.End() that calls Thread.Abort() which bubbles up the stack to end the thread. Under rare circumstances the call to Response.End() actually doesn't call Thread.Abort(), but instead calls HttpApplication.CompleteRequest(). (See this Microsoft Support article for details and a hint at the solution.)

Though one could argue the logic behind throwing this exception in the first place, there are a couple of ways to get around this.

The try-catch block

try
{

Response.Redirect(url);

}
catch (ThreadAbortException ex)
{
}

The first solution, and the most common one found in an internet search, is to simply wrap the call to Response.Redirect in a try-catch block. Although it appears to work I don't like it for two reasons:

  1. Processing an exception can be costly and messy
  2. The rest of the page and event chain continues to execute

Since the exception is caught the page continues to execute through it's event chain which takes up unnecessary CPU resources and potentially sends unwanted data to the client (which will be ignored since the client received a redirect).

A better way.

Since the real culprit is the call to Thread.Abort(), how can we Redirect the page without making that call? We would have to tell the client to redirect without calling Response.End() of course, and it turns out that there is an overload method of Response.Redirect() that will do exactly that.

Response.Redirect(string url, bool endResponse);

In this overload the second parameter tells the system whether to make the internal call to Response.End() or not. When this parameter is false the client is sent the redirect url, but the internal call to Response.End is skipped. This completely avoids the code that would throw the exception, but the cost is that this thread doesn't stop executing the Application events! Thankfully we can solve that problem also by duplicating the step that Response.End() takes under those rare circumstances, namely calling the HttpApplication.CompleteRequest() method.

HttpApplication.CompleteRequest() sets a variable that causes the thread to skip past most of the events in the HttpApplication event pipeline and go straight to the final event, named HttpApplication.EventEndRequest. This gracefully ends execution of the thread with a minumum of server resources.

Alain Renon pointed out to me on Gabriel Lozano-MorĂ¡n's blog that the page still sends the rendered HTML to the client. He's absolutely right. In fact, Page events still execute and the Page still processes postback events too. The event pipeline that I am talking about short circuiting with the call to HttpApplication.CompleteRequest() is not the Page event chain but the Application event chain. The Application event chain currently consists of the following events as of .NET 2.0:

ValidatePathExecutionStep
UrlMappingsExecutionStep
EventBeginRequest
EventAuthenticateRequest
EventDefaultAuthentication
EventPostAuthenticateRequest
EventAuthorizeRequest
EventPostAuthorizeRequest
EventResolveRequestCache
EventPostResolveRequestCache
MapHandlerExecutionStep
EventPostMapRequestHandler
EventAcquireRequestState
EventPostAcquireRequestState
EventPreRequestHandlerExecute
CallHandlerExecutionStep
EventPostRequestHandlerExecute
EventReleaseRequestState
EventPostReleaseRequestState
CallFilterExecutionStep
EventUpdateRequestCache
EventPostUpdateRequestCache
EventEndRequest

I've bolded CallHandlerExecutionStep because that is the step where all of the Page events take place. If you make a call to HttpApplication.CompleteRequest() from a Page event such as Page_Load then the Page events still process to completion, but all the Application events between CallHandlerExecutionStep and EventEndRequest are skipped and the thread ends gracefully.

One could argue that it doesn't matter so much that the page is still rendered since the client ignores it anyway, but Alain is correct that it does render, and that's not as efficient as we could make it. Also, if you have any PostBack processing (such as OnCommand Page event handlers) those will process and you may not want that either, but never fear there is an easy solution to both of those problems as well.

PostBack and Render Solutions? Overrides.

The idea is to create a class level variable that flags if the Page should terminate and then check the variable prior to processing your events or rendering your page. This flag should be set after the call to HttpApplication.CompleteRequest(). You can place the check for this value in every PostBack event or rendering block but that can be tedious and prone to errors, so I would recommend just overriding the RaisePostBackEvent and Render methods as in the code sample below:

private bool m_bIsTerminating = false;

protected void Page_Load(object sender, EventArgs e)
{

if (WeNeedToRedirect == true)
{

Response.Redirect(url, false);
HttpContext.Current.ApplicationInstance.CompleteRequest();
m_bIsTerminating = true;
// remember to end the method here if
// there is more code in it

return;

}

}

protected override void RaisePostBackEvent(IPostBackEventHandler sourceControl, string eventArgument)
{

if (m_bIsTerminating == false)

base.RaisePostBackEvent(sourceControl, eventArgument);

}

protected override void Render(HtmlTextWriter writer)
{

if (m_bIsTerminating == false)

base.Render(writer);

}

The Final Analysis

Initially I had recommended that you should simply replace all of your calls to Response.Redirect(url) with the Response.Redirect(url, false) and CompleteRequest() calls, but if you want to avoid postback processing and html rendering you'll need to add the overrides as well. From my recent in depth analysis of the code I can see that the most efficient way to redirect and end processing is to use the Response.Redirect(url) method and let the thread be aborted all the way up the stack, but if this exception is causing you grief as it does in many circumstances then the solution here is the next best thing.

It should also be noted that the Server.Transfer() method suffers from the same issue since it calls Response.End() internally. The good news is that it can be solved in the same way by using the solution above and replacing the call to Response.Redirect() with Server.Execute().

 

ABOVE ARTICLE HAVE BEEN TAKEN FROM

http://www.c6software.com/articles/ThreadAbortException.aspx

 

 

 

No comments: