为什么Response.Redirect会引起System.Threading.ThreadAbortException?

255
当我使用 Response.Redirect(...) 将表单重定向到一个新页面时,出现以下错误:

第一次机会异常类型 'System.Threading.ThreadAbortException' 发生在 mscorlib.dll 中
类型为 'System.Threading.ThreadAbortException' 的异常发生在 mscorlib.dll 中,但未在用户代码中处理

我的理解是该错误是由 Web 服务器中止在响应重定向调用的页面的剩余部分所致。
我知道我可以在 Response.Redirect 增加第二个参数,称之为 endResponse。如果我将 endResponse 设置为 True,则仍然会出现错误,但如果我将其设置为 False,则不会出现错误。但我相当确定这意味着 Web 服务器正在运行我重定向离开的页面的其余部分。这似乎至少是低效的。是否有更好的方法?有没有其他方法而非 Response.Redirect 或者有没有一种方法来强制旧页面停止加载,从而避免 ThreadAbortException?
10个回答

366

正确的模式是使用 endResponse=false 调用 Redirect 重载,并发出调用以告诉 IIS 管道,一旦你返回控制,它应该直接进入 EndRequest 阶段:

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

这篇博客文章由Thomas Marquardt提供了额外的细节,包括如何处理在Application_Error处理程序中重定向的特殊情况。


6
Context.ApplicationInstance.CompleteRequest(); 后执行代码。为什么?我需要根据条件从事件处理程序中返回吗? - IsmailS
4
旧版本的Redirect会抛出ThreadAbortException以防止任何随后的代码执行。新一点且更受欢迎的版本不会抛出异常,但如果处理程序中有其他代码,则需要负责提前返回控制权。 - Joel Fillmore
14
我认为更准确的说法是“第二个重载”,而不是你在评论中使用的“Redirect 的旧版本”短语,这并不像 MS 改变了实现,只是另一个重载。 - BornToCode
2
我认为这不是一个理想的模式。您要求页面不结束响应并继续执行,然后以编程方式完成请求。但是aspx页面和事件处理程序的呈现怎么办?不结束响应意味着在触发“completeRequest()”之前将完成呈现aspx页面。现在,如果我在页面中使用服务器端属性,比如会话变量来确定有效登录,如果过期,甚至在重定向之前就会抛出空异常。唯一的解决方法是将endResponse设置为true。 - Abs
1
本来想给这个答案点赞,但页面代码继续执行了。 在我的情况下,这不是理想的。最好处理或忽略“ThreadAbortException”,这样会更清洁。 - DaniDev
显示剩余2条评论

171

在ASP.Net WebForms中,Redirect问题没有简单而优雅的解决方案。你可以在粗暴的解决方案和繁琐的解决方案中选择。

粗暴的: Response.Redirect(url) 向浏览器发送重定向请求,并抛出 ThreadAbortedException 来终止当前线程。这样一来,从 Redirect()-调用开始后面的代码都不会被执行。缺点是这种做法不好,并且像这样杀死线程会对性能造成影响。此外,ThreadAbortedExceptions 将会出现在异常日志中。

繁琐的: 推荐的方式是调用 Response.Redirect(url, false) 然后再调用 Context.ApplicationInstance.CompleteRequest() 。但是,代码执行将会继续,页面生命周期中的其余事件处理程序仍将被执行。(例如,如果在 Page_Load 中执行重定向,则其余处理程序也将被执行,包括 Page_PreRender 等 - 生成的页面只是不会被发送到浏览器。可以通过在页面上设置标志,让后续事件处理程序在执行任何处理之前检查此标志来避免额外的处理。

CompleteRequest 的文档说明“导致 ASP.NET 绕过执行的 HTTP 管道链中的所有事件和筛选”。这很容易被误解。它确实会绕过更多的 HTTP 筛选器和模块,但不会绕过当前页面生命周期中的更多事件。)

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


8
我认为我听过的关于Web表单的最佳描述是“谎言酱”。 - mcfea
如果您使用dirty选项,您可以在Visual Studio中关闭ThreadAbortException的断点。 DEBUG > Exceptions.... 展开CLR > System.Threading > 取消选中System.Threading.ThreadAbortException - Jess
1
在我的情况下,这个异常并不是每次都会出现,只有在某些时候才会发生。也就是说,如果在Live应用程序中点击If和同一个按钮,它是可以工作的,但是当从其他机器点击相同的链接和相同的按钮时,它会出现System.Threading.ThreadAbortException。有任何想法为什么它不是每次都发生? - Sagar Shirke
1
如果你传递了false,并且不检查标志,那么浏览器将在头部接收到一个302状态码的整个页面 - found。恶意用户可以简单地将状态更改为200,并获得访问整个页面的权限,这非常不安全。 - jo0ls
如果 Context 无法解析,请尝试使用 HttpContext - Marcel Gruber

42

我知道我来晚了,但是只有当我的 Response.Redirect 在一个 Try...Catch 块中时,我才会遇到这个错误。

永远不要将 Response.Redirect 放入 Try...Catch 块中,这是不好的做法。

作为将 Response.Redirect 放入 Try...Catch 块的替代方法,我建议把方法/函数分成两个步骤:

  1. 在 Try...Catch 块内执行请求操作并设置一个“result”值以指示操作的成功或失败。

  2. 在 Try...Catch 块之外根据“result”值进行重定向(或不进行重定向)。

该代码远非完美,可能不应复制,因为我没有测试过。

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.
    }
}

1
直到我在代码中加入了try, catch,才出现了这个问题... 我想知道其他调用.NET时会导致这种行为的代码。 - interesting-name-here
这也会发生在if-else语句中,而不仅仅是try-catch。我认为它是任何条件语句,甚至在任何事件处理程序中都可能发生。无论如何,执行您建议的更改并不能解决问题,因为它仍然在条件语句和事件处理程序内部。 - TylerH

9

Response.Redirect()会抛出一个异常来中止当前请求。

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

对于Response.Redirect(),存在一种重载:

Response.Redirect(String url, bool endResponse)

如果传入 endResponse=false,则不会抛出异常(但运行时将继续处理当前请求)。
如果传入 endResponse=true(或使用另一种重载),则会抛出异常并立即终止当前请求。

8

以下是关于该问题的官方解释(我找不到最新的版本,但我认为情况对于较新的 .net 版本并没有改变):official line


8
不论链接失效的问题,仅包含链接的回答并不是一个好的回答。http://meta.stackexchange.com/q/8231 我认为链接很棒,但在你的回答中它们永远不应该是唯一的信息来源。 - Ryan Gates

8
这只是Response.Redirect(url, true)的工作方式。它会抛出ThreadAbortException来中止线程。忽略该异常即可。(我假设这是某个全局错误处理程序/记录器,您在其中看到它?)
一个有趣的相关讨论:Response.End()是否被认为是有害的?

4
终止一个线程似乎是一种过于粗暴的应对响应过早结束的方式。我发现这个框架不选择重复使用该线程,而是启动一个新线程来替代它,这让我感到很奇怪。 - spender

3

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

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();
    }

所以,如果需要在重定向后防止代码执行。
try
{
   //other code
   Response.Redirect("")
  // code not to be executed
}
catch(ThreadAbortException){}//do there id nothing here
catch(Exception ex)
{
  //Logging
}

2
只需跟进Jorge的回答即可。这实际上会移除线程中止异常的记录。 - Maxim Lavrov
当有人问为什么会出现异常时,告诉他只需使用 try..catch 进行尝试并不是一个答案。请参阅已接受的答案。我在审核“晚回答”时评论了您的答案。 - manuell
1
这与在 Response.Redirect 的第二个参数中放置 false 的效果相同,但“false”是一个比捕获 ThreadAbortException 更好的解决方法。我认为没有任何好理由以这种方式处理。 - NickG
这修复了我的问题,我有一个 async 方法,虽然已经捕获了 ThreadAbortException,但仍未被捕获。最终,我添加了一个扩展方法到 HttpResponse 中。 - TechyGypo

3

为了避免手动终止线程,我甚至尝试避免这种情况,但我宁愿使用“CompleteRequest”并继续前进——我的代码在重定向后仍有返回命令。所以这可以完成。

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();
}

也许在 public class MyBasePage : Page 中有一个 protected method - Kiquenet
不是 System.Web.UI.HttpContext,而是 System.Web.HttpContext - Kiquenet

1
我所做的是捕获此异常,以及其他可能的异常。希望这能帮助到某些人。
 catch (ThreadAbortException ex1)
 {
    writeToLog(ex1.Message);
 }
 catch(Exception ex)
 {
     writeToLog(ex.Message);
 }

2
比起 捕获并什么都不做 ,最好避免 ThreadAbortException 异常 - Kiquenet

-1

我也遇到过这个问题。

尝试使用Server.Transfer而不是Response.Redirect

对我有用。


2
Server.Transfer仍然应该抛出ThreadAbortException:http://support.microsoft.com/kb/312629,因此这不是一个推荐的解决方案。 - Joel Beckham
9
Server.Transfer 不会将重定向发送给用户。它有完全不同的目的! - Marcel
1
Server.transfer和responce.redirect是不同的。 - mzonerz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接