【问题标题】:Why Response.Redirect causes System.Threading.ThreadAbortException?为什么 Response.Redirect 会导致 System.Threading.ThreadAbortException?
【发布时间】:2019-12-01 01:15:05
【问题描述】:

当我使用 Response.Redirect(...) 将我的表单重定向到新页面时,我收到错误:

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

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

我知道我可以向Response.Redirect 添加第二个参数,称为endResponse。如果我将 endResponse 设置为 True,我仍然会收到错误,但如果我将其设置为 False,那么我不会。我很确定这意味着网络服务器正在运行我重定向的页面的其余部分。至少可以说这似乎是低效的。有一个更好的方法吗? Response.Redirect 以外的其他东西,或者有没有办法强制旧页面停止加载,而我不会得到 ThreadAbortException

【问题讨论】:

    标签: c# asp.net .net-3.5


    【解决方案1】:

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

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

    来自 Thomas Marquardt 的This blog post 提供了更多详细信息,包括如何在 Application_Error 处理程序中处理重定向的特殊情况。

    【讨论】:

    • 它在Context.ApplicationInstance.CompleteRequest();之后执行代码。为什么?我必须有条件地从事件处理程序中 return 吗?
    • @Ismail:旧版本的 Redirect 会抛出 ThreadAbortException 以阻止执行任何后续代码。较新的首选版本不会抛出异常,但如果您在处理程序中有其他代码,则您有责任尽早返回控制权。
    • 我认为说“第二次重载”而不是您在评论中使用的 The old version of Redirect 短语更准确,这不像 MS 改变了实现,它只是另一个重载。
    • 我不认为这是一个理想的模式。您要求页面不要结束响应并继续执行,然后以编程方式完成请求。但是 aspx 页面和事件处理程序的呈现呢?不结束响应意味着,它将在点击“completeRequest()”之前完成渲染 aspx 页面。现在,如果我在我的页面中使用服务器端属性,请说一个会话变量来确定有效登录,如果过期将在重定向之前引发空异常。解决这个问题的唯一方法是让 endResponse 恢复为 true。
    • 本打算对这个答案投赞成票,但该页面代码继续执行。在我的情况下,这并不理想。处理或忽略“ThreadAbortException”要干净得多
    【解决方案2】:

    对于 ASP.Net WebForms 中的Redirect 问题,没有简单而优雅的解决方案。您可以在 Dirty 解决方案和 Tedious 解决方案之间进行选择

    DirtyResponse.Redirect(url) 向浏览器发送重定向,然后抛出ThreadAbortedException 以终止当前线程。因此,在 Redirect() 调用之后不会执行任何代码。缺点:像这样杀死线程是不好的做法,并且会影响性​​能。此外,ThreadAbortedExceptions 将显示在异常日志中。

    繁琐:推荐的方法是先调用Response.Redirect(url, false),然后再调用Context.ApplicationInstance.CompleteRequest(),但是代码会继续执行,页面生命周期中的其余事件处理程序仍会执行。 (例如,如果您在 Page_Load 中执行重定向,不仅会执行处理程序的其余部分,还会调用 Page_PreRender 等 - 渲染的页面不会发送到浏览器。您可以通过以下方式避免额外的处理例如在页面上设置一个标志,然后让后续的事件处理程序在进行任何处理之前检查这个标志。

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

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

    【讨论】:

    • 我相信我听过的对网络表单最好的描述是“谎言酱”。
    • 如果您使用 dirty 选项,您可以在 Visual Studio 中关闭 ThreadAbortException 的中断。 调试 > 异常...。展开 CLR > System.Threading > 取消选中 System.Threading.ThreadAbortException
    • 在我的情况下,这个异常并不是每次都会出现,只是在它之间发生了几次。意味着如果单击 Live 应用程序的相同按钮,它正在工作,但是当从其他机器单击相同的链接和相同的按钮时,它会给出 System.Threading.ThreadAbortException。知道为什么它不是每次都发生吗??
    • 如果您传递 false,并且不费心检查标志,那么浏览器将收到整个页面的标题中带有 302 状态代码 - 已找到。恶意用户只需将状态更改为 200,就可以访问整个页面,这是非常不安全的。
    • 如果Context 无法为您解决问题,请尝试HttpContext
    【解决方案3】:

    我知道我迟到了,但是如果我的 Response.Redirect 位于 Try...Catch 块中,我只会遇到此错误。

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

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

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

    2. 在 Try...Catch 块之外是否进行重定向取决于“结果”值是什么。

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

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

    【讨论】:

    • 直到我将我的代码包装在 try,catch...
    • 这也将发生在 if-else 语句中,而不仅仅是 try-catch。我相信这是任何条件语句,甚至可能在任何事件处理程序中。无论如何,执行您建议的更改并不能解决问题,因为它仍在条件和事件处理程序中。
    【解决方案4】:

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

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

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

    Response.Redirect(String url, bool endResponse)
    

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

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

    【讨论】:

      【解决方案5】:

      这是关于问题的official line(我找不到最新的,但我认为对于更高版本的 .net,情况没有改变)

      【讨论】:

      • @svick 不管链接失效如何,仅链接的答案并不是很好的答案。 meta.stackexchange.com/q/8231I think that links are fantastic, but they should never be the only piece of information in your answer.
      【解决方案6】:

      这就是Response.Redirect(url, true) 的工作原理。它抛出ThreadAbortException 以中止线程。忽略那个例外。 (我认为它是您在哪里看到的一些全局错误处理程序/记录器?)

      一个有趣的相关讨论Is Response.End() Considered Harmful?

      【讨论】:

      • 中止线程似乎是处理过早结束响应的一种非常严厉的方法。我觉得奇怪的是,框架不愿意重用线程而不是旋转一个新线程来代替它。
      【解决方案7】:

      我什至试图避免这种情况,以防万一手动在线程上执行中止,但我宁愿将它留在“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中的受保护方法
      • 不是System.Web.UI.HttpContext。真的System.Web.HttpContext
      【解决方案8】:

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

      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
      }
      

      【讨论】:

      • 只需跟进豪尔赫的回答。这将最终删除线程中止异常的日志记录。
      • 当有人问他为什么得到一个异常时,告诉他只玩 try..catch 不是答案。请参阅已接受的答案。我在查看“迟到的答案”时评论了您的答案
      • 这与为 Response.Redirect 的第二个参数设置 false 具有相同的效果,但“false”是比捕获 ThreadAbortException 更好的解决方案。我认为没有充分的理由这样做。
      【解决方案9】:

      我所做的是捕获这个异常以及另一个可能的异常。希望这对某人有所帮助。

       catch (ThreadAbortException ex1)
       {
          writeToLog(ex1.Message);
       }
       catch(Exception ex)
       {
           writeToLog(ex.Message);
       }
      

      【讨论】:

      • 避免 ThreadAbortException exception什么都不做 更好?
      【解决方案10】:

      我也有这个问题。

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

      为我工作。

      【讨论】:

      • Server.Transfer 仍应抛出 ThreadAbortException:support.microsoft.com/kb/312629,因此不推荐使用此解决方案。
      • Server.Transfer 不会向用户发送重定向。它有完全不同的目的!
      • Server.transfer 和 responce.redirect 不同
      猜你喜欢
      • 1970-01-01
      • 2010-12-27
      • 1970-01-01
      • 2013-02-25
      • 2012-12-31
      • 2014-01-01
      • 2017-03-10
      • 1970-01-01
      相关资源
      最近更新 更多