Por que Response.Redirect causa System.Threading.ThreadAbortException?

230

Quando uso o Response.Redirect (...) para redirecionar meu formulário para uma nova página, recebo o erro:

Ocorreu uma primeira exceção do tipo 'System.Threading.ThreadAbortException' no mscorlib.dll.
Uma exceção do tipo 'System.Threading.ThreadAbortException' ocorreu no mscorlib.dll, mas não foi tratada no código do usuário.

Meu entendimento disso é que o erro está sendo causado pelo servidor da web anulando o restante da página em que o response.redirect foi chamado.

Eu sei que posso adicionar um segundo parâmetro ao Response.Redirectchamado endResponse. Se eu definir endResponse como True, ainda recebo o erro, mas se o definir como False, não o faço. Tenho certeza, porém, de que isso significa que o servidor da web está executando o restante da página da qual eu redirecionei. O que parece ser ineficiente para dizer o mínimo. Existe uma maneira melhor de fazer isso? Algo diferente Response.Redirectou existe uma maneira de forçar a página antiga a parar de carregar onde eu não receberei um ThreadAbortException?

Ben Hoffman
fonte

Respostas:

332

O padrão correto é chamar a sobrecarga de redirecionamento com endResponse = false e fazer uma chamada para informar ao pipeline do IIS que ele deve avançar diretamente para o estágio EndRequest assim que você retornar o controle:

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

Esta postagem no blog de Thomas Marquardt fornece detalhes adicionais, incluindo como lidar com o caso especial de redirecionamento dentro de um manipulador Application_Error.

Joel Fillmore
fonte
6
Ele executa o código depois Context.ApplicationInstance.CompleteRequest();. Por quê? Vou precisar returndo manipulador de eventos condicionalmente?
Ismails
4
@Ismail: a versão antiga do Redirect lança uma ThreadAbortException para impedir a execução de qualquer código subsequente. A versão preferida mais recente não é lançada, mas você é responsável por retornar o controle mais cedo se tiver código adicional no manipulador.
Joel Fillmore
12
Eu acho que é mais preciso dizer "a segunda sobrecarga", em vez da The old version of Redirectfrase que você usa no seu comentário, não é como se a MS tivesse alterado a implementação, é apenas outra sobrecarga.
BornToCode
2
Eu não acho que esse seja um padrão ideal. Você está pedindo que a página não encerre a resposta e continue a execução e, em seguida, conclua a solicitação programaticamente. Mas e a renderização de página aspx e manipuladores de eventos? não terminando o meio de resposta, ele terminará de renderizar a página aspx antes de pressionar "completeRequest ()". Agora, se eu estiver usando uma propriedade do lado do servidor em minha página, diga uma variável de sessão para determinar o login válido, que se expirar lançará uma exceção nula antes mesmo de redirecionar. E a única maneira de corrigir isso é fazer com que a resposta final volte a ser verdadeira.
Abs
1
Iria votar esta resposta, mas o código da página continuava sendo executado. Isso não é ideal no meu caso. Muito mais limpo para lidar com ou ignorar a "ThreadAbortException"
DaniDev
159

Não solução simples e elegante para o Redirectproblema no ASP.Net WebForms. Você pode escolher entre a solução Dirty e a solução Tedious

Sujo : Response.Redirect(url)envia um redirecionamento para o navegador e lança a ThreadAbortedExceptionpara finalizar o segmento atual. Portanto, nenhum código é executado após a chamada Redirect (). Desvantagens: É uma prática ruim e tem implicações de desempenho para eliminar threads como este. Além disso, ThreadAbortedExceptionsaparecerá no log de exceção.

Tedioso : A maneira recomendada é chamar Response.Redirect(url, false)e, no Context.ApplicationInstance.CompleteRequest()entanto, a execução do código continuará e o restante dos manipuladores de eventos no ciclo de vida da página ainda será executado. (Por exemplo, se você executar o redirecionamento no Page_Load, o resto do manipulador não será executado, o Page_PreRender e assim por diante também serão chamados - a página renderizada não será enviada ao navegador. Você pode evitar o processamento extra por exemplo, definir um sinalizador na página e permitir que os manipuladores de eventos subsequentes verifiquem esse sinalizador antes de realizar qualquer processamento.

(A documentação CompleteRequestafirma que "faz com que o ASP.NET ignore todos os eventos e a filtragem na cadeia de execução do pipeline HTTP ". Isso pode ser facilmente mal interpretado. Ignora outros filtros e módulos HTTP, mas não ignora outros eventos no ciclo de vida da página atual .)

O problema mais profundo é que o WebForms não possui um nível de abstração. Quando você está em um manipulador de eventos, já está no processo de criar uma página para saída. Redirecionar em um manipulador de eventos é feio porque você está encerrando uma página parcialmente gerada para gerar uma página diferente. O MVC não tem esse problema, pois o fluxo de controle é separado das visualizações de renderização, portanto, você pode fazer um redirecionamento limpo simplesmente retornando a RedirectActionno controlador, sem gerar uma visualização.

JacquesB
fonte
7
Acredito que a melhor descrição dos formulários da web que já ouvi foi "molho mentiroso".
Mcfea
9
Eu amo a quantidade de detalhes nesta resposta. Melhor do que a resposta aceita
Jess
Se você usar a opção suja , poderá desativar a interrupção no ThreadAbortException no Visual Studio. DEBUG> Exceções ... . Expanda CLR> System.Threading> Desmarque System.Threading.ThreadAbortException .
Jess
graças a deus temos alguém com resposta adequada, e essa deve ser a resposta mais votada.
Abs
1
No meu caso, essa exceção não está ocorrendo todas as vezes, apenas algumas vezes entre elas está ocorrendo. Significa Se, e ao clicar no mesmo botão do aplicativo Live, ele está funcionando, mas quando o mesmo link e o mesmo botão são clicados em outra máquina, ele fornece System.Threading.ThreadAbortException. Alguma idéia de por que isso não acontece toda vez?
precisa saber é o seguinte
33

Sei que estou atrasado, mas só tive esse erro se o meu Response.Redirectestiver em um Try...Catchbloco.

Nunca coloque um Response.Redirect em um bloco Try ... Catch. É uma prática ruim

Editar

Em resposta ao comentário de @ Kiquenet, aqui está o que eu faria como uma alternativa para colocar o Response.Redirect no bloco Try ... Catch.

Eu dividiria o método / função em duas etapas.

A primeira etapa do bloco Try ... Catch executa as ações solicitadas e define um valor de "resultado" para indicar sucesso ou falha das ações.

A etapa dois fora do bloco Try ... Catch faz o redirecionamento (ou não) dependendo do valor do "resultado".

Este código está longe de ser perfeito e provavelmente não deve ser copiado, pois eu não o testei

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.
    }
}
Ortund
fonte
@Kiquenet, veja minha resposta atualizada para um exemplo do que eu faria. Para não dizer que é o melhor caminho, mas acho que é uma alternativa viável.
Ortund 3/11
Não têm o problema até que eu envolvi meu código em um try, catch ... Gostaria de saber o que as outras chamadas de código causar esse comportamento em .NET
interessante-name-aqui
8

Response.Redirect() lança uma exceção para abortar a solicitação atual.

Este artigo da KB descreve esse comportamento (também para os métodos Request.End()e Server.Transfer()).

Pois Response.Redirect()existe uma sobrecarga:

Response.Redirect(String url, bool endResponse)

Se você passar endResponse = false , a exceção não será lançada (mas o tempo de execução continuará processando a solicitação atual).

Se endResponse = true (ou se a outra sobrecarga for usada), a exceção será lançada e a solicitação atual será imediatamente encerrada.

M4N
fonte
7

Aqui está a linha oficial do problema (não encontrei a mais recente, mas não acho que a situação tenha mudado para versões posteriores do .net)

gastador
fonte
5
@svick Independentemente da podridão do link, as respostas apenas para links não são realmente ótimas. meta.stackexchange.com/q/8231 I think that links are fantastic, but they should never be the only piece of information in your answer.
Ryan Gates
7

É assim que Response.Redirect(url, true) funciona. Ele lança o ThreadAbortExceptionpara abortar a discussão. Apenas ignore essa exceção. (Presumo que seja algum manipulador / registrador de erros global onde você o vê?)

Uma discussão relacionada interessante é Response.End()considerada prejudicial? .

Martin Smith
fonte
4
Interromper um tópico parece uma maneira muito complicada de lidar com o final prematuro da resposta. Acho estranho que a estrutura não prefira reutilizar o encadeamento em vez de criar um novo para substituí-lo.
gastador 5/05
3

Também tentei outra solução, mas parte do código foi executado após o redirecionamento.

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

Portanto, se for necessário impedir a execução do código após o redirecionamento

try
{
   //other code
   Response.Redirect("")
  // code not to be executed
}
catch(ThreadAbortException){}//do there id nothing here
catch(Exception ex)
{
  //Logging
}
Maxim Lavrov
fonte
1
basta seguir a resposta de Jorge. Isso removerá efetivamente o log da exceção de cancelamento de thread.
Maxim Lavrov
Quando alguém pergunta por que ele recebe uma exceção, dizer a ele para brincar com try..catch não é uma resposta. Veja a resposta aceita. Eu comentei a sua resposta enquanto revia a "resposta tardia"
manuell
Isso tem o mesmo efeito que colocar false para o segundo argumento de Response.Redirect, mas o "false" é uma solução melhor do que capturar o ThreadAbortException. Não vejo que haja uma boa razão para fazer dessa maneira.
NickG
2

Eu até tentei evitar isso, apenas no caso de fazer o Abort no thread manualmente, mas prefiro deixá-lo com o "CompleteRequest" e seguir em frente - meu código tem comandos de retorno após redirecionamentos de qualquer maneira. Então, isso pode ser feito

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

O que faço é capturar essa exceção, juntamente com outras possíveis exceções. Espero que isso ajude alguém.

 catch (ThreadAbortException ex1)
 {
    writeToLog(ex1.Message);
 }
 catch(Exception ex)
 {
     writeToLog(ex.Message);
 }
Jorge
fonte
2
Melhor evitar a exceção ThreadAbortException do que pegar e não fazer nada ?
Kiquenet 5/10
-1

Eu também tive esse problema.

Tente usar em Server.Transfervez deResponse.Redirect

Trabalhou para mim.

Marko
fonte
2
Server.Transfer ainda deve lançar uma ThreadAbortException: support.microsoft.com/kb/312629 , portanto, não é uma solução recomendada.
Joel Beckham
9
Server.Transfer não enviará um redirecionamento para o usuário. Tem um propósito completamente diferente!
Marcel
1
Server.transfer e responce.redirect são diferentes
mzonerz