关于c#:为什么Response.Redirect会导致System.Threading.ThreadAbortException?

Why Response.Redirect causes System.Threading.ThreadAbortException?

当我使用response.redirect(…)将表单重定向到新页面时,会收到错误:

A first chance exception of type 'System.Threading.ThreadAbortException' occurred in mscorlib.dll
An exception of type 'System.Threading.ThreadAbortException' occurred in mscorlib.dll but was not handled in user code

我对此的理解是,错误是由Web服务器中止调用response.redirect的页面的其余部分引起的。

我知道我可以向Response.Redirect添加第二个参数,称为endresponse。如果我将endresponse设置为true,我仍然会得到错误,但是如果我将其设置为false,那么我不会。虽然这意味着Web服务器正在运行我重定向的页面的其余部分,但我非常确定。至少可以说,这似乎是低效的。有更好的方法吗?除了Response.Redirect以外的东西,或者有什么方法可以强制旧页面停止加载,而我将无法得到ThreadAbortException吗?


正确的模式是调用endResponse=false的重定向重载,并进行调用,告诉IIS管道,一旦返回控件,它应该直接前进到endRequest阶段:

1
2
Response.Redirect(url, false);
Context.ApplicationInstance.CompleteRequest();

ThomasMarquardt的这篇博客文章提供了更多的细节,包括如何处理应用程序错误处理程序中重定向的特殊情况。


对于ASP.NET WebForms中的Redirect问题,没有简单而优雅的解决方案。你可以在肮脏的解决方案和乏味的解决方案之间做出选择。

脏:Response.Redirect(url)向浏览器发送重定向,然后抛出ThreadAbortedException终止当前线程。因此没有执行过redirect()-调用的代码。缺点:这是不好的实践,并且会影响性能,从而杀死这样的线程。另外,ThreadAbortedExceptions将出现在异常日志中。

乏味:建议的方法是调用Response.Redirect(url, false),然后调用Context.ApplicationInstance.CompleteRequest()。但是,代码执行将继续,页面生命周期中的其余事件处理程序仍将执行。(例如,如果在页面加载中执行重定向,不仅会执行其余处理程序,还会调用页面预呈现等-呈现的页面不会发送到浏览器。您可以通过在页面上设置标志来避免额外的处理,然后让后续的事件处理程序在进行任何处理之前检查该标志。

(CompleteRequest的文档指出它"导致ASP.NET绕过HTTP执行管道链中的所有事件和筛选"。这很容易被误解。它确实绕过了进一步的HTTP过滤器和模块,但在当前页面生命周期中不会绕过进一步的事件。)

更深层的问题是,WebForms缺乏抽象层次。当您在事件处理程序中时,您已经在构建要输出的页面的过程中。在事件处理程序中重定向是丑陋的,因为您要终止部分生成的页面以生成不同的页面。MVC没有这个问题,因为控制流与呈现视图是分开的,所以您可以通过简单地在控制器中返回RedirectAction,而不生成视图来执行干净的重定向。


我知道我迟到了,但我只有在我的Response.RedirectTry...Catch块的时候才会犯这个错误。

永远不要放响应。重定向到一个try…catch块。这是不好的做法

编辑

作为对@kiquenet评论的回应,下面是我将要做的作为放置响应的替代方法。重定向到try…catch块。

我将把方法/函数分解为两个步骤。

Try…Catch块中的步骤1执行请求的操作,并设置"result"值以指示操作的成功或失败。

Try…Catch块之外的步骤2根据"result"值进行重定向(或不重定向)。

这段代码还远远不够完美,可能不应该复制,因为我还没有测试过它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public void btnLogin_Click(UserLoginViewModel model)
{
    bool ValidLogin = false; // this is our"result value"
    try
    {
        using (Context Db = new Context)
        {
            User User = new User();

            if (String.IsNullOrEmpty(model.EmailAddress))
                ValidLogin = false; // no email address was entered
            else
                User = Db.FirstOrDefault(x => x.EmailAddress == model.EmailAddress);

            if (User != null && User.PasswordHash == Hashing.CreateHash(model.Password))
                ValidLogin = true; // login succeeded
        }
    }
    catch (Exception ex)
    {
        throw ex; // something went wrong so throw an error
    }

    if (ValidLogin)
    {
        GenerateCookie(User);
        Response.Redirect("~/Members/Default.aspx");
    }
    else
    {
        // do something to indicate that the login failed.
    }
}


Response.Redirect()抛出异常以中止当前请求。

这篇知识库文章描述了这种行为(也适用于Request.End()Server.Transfer()方法)。

对于Response.Redirect()存在过载:

1
Response.Redirect(String url, bool endResponse)

如果传递endResponse=false,则不会引发异常(但运行时将继续处理当前请求)。

如果endResponse=true(或者使用了其他重载),则抛出异常,当前请求将立即终止。


这就是response.redirect(url,true)的工作原理。它抛出threadabortexception以中止线程。忽略那个例外。(我假设您看到的是某个全局错误处理程序/记录器?)

一个有趣的相关讨论是response.end()被认为有害吗?


这是关于这个问题的官方说法(我找不到最新版本,但我认为.NET的更新版本不会改变这种情况)。


我甚至试图避免这种情况,以防手动中止线程,但我宁愿把它放在"completeRequest"中,继续前进-我的代码在重定向之后仍然有返回命令。这样就可以做到

1
2
3
4
5
public static void Redirect(string VPathRedirect, global::System.Web.UI.Page Sender)
{
    Sender.Response.Redirect(VPathRedirect, false);
    global::System.Web.UI.HttpContext.Current.ApplicationInstance.CompleteRequest();
}

我还尝试了其他的解决方案,但是在重定向之后执行了一些代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void ResponseRedirect(HttpResponse iResponse, string iUrl)
    {
        ResponseRedirect(iResponse, iUrl, HttpContext.Current);
    }

    public static void ResponseRedirect(HttpResponse iResponse, string iUrl, HttpContext iContext)
    {
        iResponse.Redirect(iUrl, false);

        iContext.ApplicationInstance.CompleteRequest();

        iResponse.BufferOutput = true;
        iResponse.Flush();
        iResponse.Close();
    }

因此,如果需要在重定向后阻止代码执行

1
2
3
4
5
6
7
8
9
10
11
try
{
   //other code
   Response.Redirect("")
  // code not to be executed
}
catch(ThreadAbortException){}//do there id nothing here
catch(Exception ex)
{
  //Logging
}


我要做的是捕获这个异常,以及其他可能的异常。希望这能帮助别人。

1
2
3
4
5
6
7
8
 catch (ThreadAbortException ex1)
 {
    writeToLog(ex1.Message);
 }
 catch(Exception ex)
 {
     writeToLog(ex.Message);
 }


我也有那个问题。尝试使用Server.Transfer而不是Response.Redirect为我工作