¿Por qué Response.Redirect causa System.Threading.ThreadAbortException?

230

Cuando uso Response.Redirect (...) para redirigir mi formulario a una nueva página, aparece el error:

Se produjo
una excepción de primera oportunidad del tipo 'System.Threading.ThreadAbortException' en mscorlib.dll Una excepción del tipo 'System.Threading.ThreadAbortException' ocurrió en mscorlib.dll pero no se manejó en el código de usuario

Según tengo entendido, el servidor web abortó el resto de la página en la que se llamó a response.redirect.

Sé que puedo agregar un segundo parámetro Response.Redirectque se llama endResponse. Si configuro endResponse en True, sigo obteniendo el error, pero si lo configuro en False, no lo hago. Sin embargo, estoy bastante seguro de que eso significa que el servidor web está ejecutando el resto de la página desde la que redireccioné. Lo que parecería ser ineficiente por decir lo menos. ¿Hay una mejor manera de hacer esto? ¿ Response.RedirectHay algo diferente o hay alguna forma de obligar a la página anterior a dejar de cargarse donde no obtendré un ThreadAbortException?

Ben Hoffman
fuente

Respuestas:

332

El patrón correcto es llamar a la sobrecarga de redireccionamiento con endResponse = false y hacer una llamada para decirle a la canalización de IIS que debe avanzar directamente a la etapa EndRequest una vez que devuelva el control:

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

Esta publicación de blog de Thomas Marquardt proporciona detalles adicionales, incluido cómo manejar el caso especial de redireccionamiento dentro de un controlador Application_Error.

Joel Fillmore
fuente
66
Ejecuta código después Context.ApplicationInstance.CompleteRequest();. ¿Por qué? ¿Tendré que hacerlo returndesde el controlador de eventos condicionalmente?
IsmailS
44
@Ismail: la versión anterior de Redirect lanza una ThreadAbortException para evitar la ejecución de cualquier código posterior. La versión más nueva y preferida no se lanza, pero usted es responsable de devolver el control temprano si tiene un código adicional en el controlador.
Joel Fillmore
12
Creo que es más exacto decir "la segunda sobrecarga" en lugar de la The old version of Redirectfrase que usas en tu comentario, no es que MS haya cambiado la implementación, es solo otra sobrecarga.
BornToCode
2
No creo que este sea un patrón ideal. Le está pidiendo a la página que no finalice la respuesta y continúe la ejecución y luego complete la solicitud mediante programación. Pero, ¿qué pasa con la representación de la página aspx y los controladores de eventos? no finalizar la respuesta significa que finalizará la representación de la página aspx antes de presionar "completeRequest ()". Ahora, si estoy usando una propiedad del lado del servidor en mi página, diga una variable de sesión para determinar un inicio de sesión válido, que si expira arrojará una excepción nula incluso antes de redirigir. Y la única forma de solucionarlo es hacer que endResponse vuelva a ser verdadero.
Abs
1
Iba a votar esta respuesta, pero ese código de página continuó ejecutándose. Esto no es ideal en mi caso. Mucho más limpio para manejar o ignorar la "ThreadAbortException"
DaniDev
159

No hay una solución simple y elegante al Redirectproblema en ASP.Net WebForms. Puede elegir entre la solución sucia y la solución tediosa

Sucio : Response.Redirect(url)envía una redirección al navegador y luego lanza una ThreadAbortedExceptionpara terminar el hilo actual. Por lo tanto, no se ejecuta ningún código más allá de la llamada Redirect (). Desventajas: es una mala práctica y tiene implicaciones de rendimiento para matar hilos como este. Además, ThreadAbortedExceptionsaparecerá en el registro de excepciones.

Tedioso : la forma recomendada es llamar Response.Redirect(url, false)y, sin Context.ApplicationInstance.CompleteRequest()embargo, la ejecución del código continuará y el resto de los controladores de eventos en el ciclo de vida de la página aún se ejecutarán. (Por ejemplo, si realiza la redirección en Page_Load, no solo se ejecutará el resto del controlador, Page_PreRender, etc., sino que también se invocará: la página representada simplemente no se enviará al navegador. Puede evitar el procesamiento adicional al por ejemplo, establecer un indicador en la página y luego permitir que los controladores de eventos posteriores verifiquen este indicador antes de realizar cualquier procesamiento.

(La documentación CompleteRequestindica que "hace que ASP.NET omita todos los eventos y el filtrado en la cadena de ejecución de la canalización HTTP ". Esto puede ser fácilmente malinterpretado. Omite más filtros y módulos HTTP, pero no omite más eventos en el ciclo de vida actual de la página ).

El problema más profundo es que WebForms carece de un nivel de abstracción. Cuando está en un controlador de eventos, ya está en el proceso de crear una página para la salida. La redirección en un controlador de eventos es fea porque está finalizando una página parcialmente generada para generar una página diferente. MVC no tiene este problema, ya que el flujo de control es independiente de las vistas de representación, por lo que puede hacer una redirección limpia simplemente devolviendo un RedirectActionen el controlador, sin generar una vista.

JacquesB
fuente
77
Creo que la mejor descripción de los formularios web que escuché fue "salsa de mentiras".
mcfea
99
Me encanta la cantidad de detalles en esta respuesta. Mejor que la respuesta aceptada
Jess
Si usa la opción sucia , puede desactivar el descanso en ThreadAbortException en Visual Studio. Test> Excepciones ... . Expanda CLR> System.Threading> Desmarque System.Threading.ThreadAbortException .
Jess
gracias a Dios tenemos a alguien con la respuesta adecuada, y esta debería ser la respuesta más votada.
Abs
1
En mi caso, esta excepción no llega cada vez, solo ocurre unas pocas veces. Significa que si hace clic en el mismo botón de la aplicación Live está funcionando, pero cuando se hace clic en el mismo enlace y el mismo botón desde otra máquina, está dando System.Threading.ThreadAbortException. ¿Alguna idea de por qué no sucede todo el tiempo?
Sagar Shirke
33

Sé que llego tarde, pero solo he tenido este error si mi Response.Redirectestá en un Try...Catchbloque.

Nunca ponga una Response.Redirect en un bloque Try ... Catch. Es una mala practica

Editar

En respuesta al comentario de @ Kiquenet, esto es lo que haría como alternativa a poner el Response.Redirect en el bloque Try ... Catch.

Rompería el método / función en dos pasos.

El primer paso dentro del bloque Try ... Catch realiza las acciones solicitadas y establece un valor de "resultado" para indicar el éxito o el fracaso de las acciones.

Paso dos fuera del bloque Try ... Catch hace la redirección (o no) dependiendo de cuál sea el valor del "resultado".

Este código está lejos de ser perfecto y probablemente no debería copiarse ya que no lo he probado

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
fuente
@Kiquenet, vea mi respuesta actualizada para ver un ejemplo de lo que haría. No quiere decir que sea el mejor curso, pero creo que es una alternativa viable.
Ortund
No tuve el problema hasta que envolví mi código en un intento, atrape ... Me pregunto qué otras llamadas de código causan este comportamiento en .NET
interesante-nombre-aquí
8

Response.Redirect() lanza una excepción para abortar la solicitud actual.

Este artículo de KB describe este comportamiento (también para los métodos Request.End()y Server.Transfer()).

Porque Response.Redirect()existe una sobrecarga:

Response.Redirect(String url, bool endResponse)

Si pasa endResponse = false , entonces no se genera la excepción (pero el tiempo de ejecución continuará procesando la solicitud actual).

Si endResponse = true (o si se usa la otra sobrecarga), se genera la excepción y la solicitud actual se terminará de inmediato.

M4N
fuente
7

Aquí está la línea oficial sobre el problema (no pude encontrar la última versión, pero no creo que la situación haya cambiado para versiones posteriores de .net)

gastador
fuente
55
@svick Independientemente de la rotura del enlace, las respuestas de solo enlace no son realmente excelentes. 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

Así es como Response.Redirect(url, true) funciona. Lanza el ThreadAbortExceptionpara abortar el hilo. Solo ignora esa excepción. (Supongo que es un controlador / registrador de errores global donde lo ves)

Una discusión relacionada interesante ¿Se Response.End()considera perjudicial? .

Martin Smith
fuente
44
Anular un hilo parece una forma realmente dura de lidiar con el final prematuro de la respuesta. Me parece extraño que el marco no prefiera reutilizar el hilo en lugar de girar uno nuevo para tomar su lugar.
gastador
3

También probé otra solución, pero parte del código ejecutado después de la redirección.

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

Entonces, si es necesario evitar la ejecución del código después de la redirección

try
{
   //other code
   Response.Redirect("")
  // code not to be executed
}
catch(ThreadAbortException){}//do there id nothing here
catch(Exception ex)
{
  //Logging
}
Maxim Lavrov
fuente
1
solo sigue la respuesta de Jorge. Esto eliminará el registro de la excepción de anulación de subprocesos.
Maxim Lavrov
Cuando alguien le pregunta por qué obtiene una Excepción, decirle que solo juegue con try..catch no es una respuesta. Ver la respuesta aceptada. Comenté tu respuesta mientras revisaba "respuesta tardía"
manuell
Eso tiene el mismo efecto que poner falso para el segundo argumento de Response.Redirect, pero el "falso" es una mejor solución que capturar ThreadAbortException. No veo que haya una buena razón para hacerlo de esta manera.
NickG
2

incluso intenté evitar esto, por si acaso haciendo el aborto en el hilo manualmente, pero prefiero dejarlo con "CompleteRequest" y continuar: mi código tiene comandos de retorno después de las redirecciones de todos modos. Entonces esto se puede hacer

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
fuente
1

Lo que hago es detectar esta excepción, junto con otras posibles excepciones. Espero que esto ayude a alguien.

 catch (ThreadAbortException ex1)
 {
    writeToLog(ex1.Message);
 }
 catch(Exception ex)
 {
     writeToLog(ex.Message);
 }
Jorge
fuente
2
¿Es mejor evitar la excepción ThreadAbortException que catch y no hacer nada ?
Kiquenet
-1

Yo también tuve ese problema.

Intenta usar en Server.Transferlugar deResponse.Redirect

Trabajó para mi.

Marko
fuente
2
Server.Transfer aún debería lanzar una ThreadAbortException: support.microsoft.com/kb/312629 , por lo que no es una solución recomendada.
Joel Beckham
99
Server.Transfer no enviará una redirección al usuario. ¡Tiene un propósito completamente diferente!
Marcel
1
Server.transfer y responce.redirect son diferentes
mzonerz