Warum verursacht Response.Redirect die System.Threading.ThreadAbortException?

230

Wenn ich Response.Redirect (...) verwende, um mein Formular auf eine neue Seite umzuleiten, wird folgende Fehlermeldung angezeigt:

Eine erste zufällige Ausnahme vom Typ 'System.Threading.ThreadAbortException' ist in mscorlib.dll aufgetreten.
Eine Ausnahme vom Typ 'System.Threading.ThreadAbortException' ist in mscorlib.dll aufgetreten, wurde jedoch nicht im Benutzercode behandelt

Mein Verständnis davon ist, dass der Fehler dadurch verursacht wird, dass der Webserver den Rest der Seite abbricht, auf der die response.redirect aufgerufen wurde.

Ich weiß, dass ich einen zweiten Parameter hinzufügen kann, der Response.RedirectendResponse heißt. Wenn ich endResponse auf True setze, wird immer noch der Fehler angezeigt, aber wenn ich ihn auf False setze, tue ich das nicht. Ich bin mir jedoch ziemlich sicher, dass der Webserver den Rest der Seite ausführt, von der ich umgeleitet habe. Was gelinde gesagt ineffizient zu sein scheint. Gibt es einen besseren Weg, dies zu tun? Etwas anderes als Response.Redirectoder gibt es eine Möglichkeit, die alte Seite zu zwingen, das Laden zu beenden, wo ich keine bekomme ThreadAbortException?

Ben Hoffman
quelle

Antworten:

332

Das richtige Muster besteht darin, die Redirect-Überladung mit endResponse = false aufzurufen und der IIS-Pipeline mitzuteilen, dass sie direkt zur EndRequest-Phase vorrücken soll, sobald Sie die Kontrolle zurückgeben:

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

Dieser Blog-Beitrag von Thomas Marquardt enthält zusätzliche Details, einschließlich der Behandlung des Sonderfalls der Umleitung in einem Application_Error-Handler.

Joel Fillmore
quelle
6
Es führt Code nach aus Context.ApplicationInstance.CompleteRequest();. Warum? Muss ich returnvom Event-Handler bedingt sein?
IsmailS
4
@Ismail: Die alte Version von Redirect löst eine ThreadAbortException aus, um die Ausführung nachfolgender Codes zu verhindern. Die neuere, bevorzugte Version wirft nicht, aber Sie sind dafür verantwortlich, die Kontrolle frühzeitig zurückzugeben, wenn Sie zusätzlichen Code im Handler haben.
Joel Fillmore
12
Ich denke, es ist genauer, "die zweite Überlastung" zu sagen, als die The old version of RedirectPhrase, die Sie in Ihrem Kommentar verwenden. Es ist nicht so, als hätte MS die Implementierung geändert, es ist nur eine weitere Überlastung.
BornToCode
2
Ich denke nicht, dass dies ein ideales Muster ist. Sie fordern die Seite auf, die Antwort nicht zu beenden, die Ausführung fortzusetzen und die Anforderung dann programmgesteuert abzuschließen. Aber was ist mit dem Rendern von Aspx-Seiten- und Ereignishandlern? Wenn die Antwort nicht beendet wird, wird das Rendern der Aspx-Seite beendet, bevor "completeRequest ()" gedrückt wird. Wenn ich jetzt eine serverseitige Eigenschaft auf meiner Seite verwende, sagen Sie eine Sitzungsvariable, um eine gültige Anmeldung zu ermitteln. Wenn diese abläuft, wird eine Null-Ausnahme ausgelöst, bevor sie überhaupt umgeleitet wird. Die einzige Möglichkeit, dies zu beheben, besteht darin, die endResponse wieder auf true zu setzen.
Abs
1
Ich wollte diese Antwort abstimmen, aber dieser Seitencode wurde weiterhin ausgeführt. Dies ist in meinem Fall nicht ideal. Viel sauberer, um die "ThreadAbortException" zu handhaben oder zu ignorieren
DaniDev
159

In ASP.Net WebForms gibt es keine einfache und elegante Lösung für das RedirectProblem. Sie können zwischen der Dirty- Lösung und der Tedious- Lösung wählen

Dirty : Response.Redirect(url)Sendet eine Weiterleitung an den Browser und löst dann eine aus, ThreadAbortedExceptionum den aktuellen Thread zu beenden. Nach dem Aufruf von Redirect () wird also kein Code ausgeführt. Nachteile: Es ist eine schlechte Praxis und hat Auswirkungen auf die Leistung, solche Threads zu beenden. Wird ThreadAbortedExceptionsauch in der Ausnahmeprotokollierung angezeigt.

Mühsam : Die empfohlene Methode besteht darin, aufzurufen Response.Redirect(url, false)und dann die Context.ApplicationInstance.CompleteRequest()Codeausführung fortzusetzen. Die restlichen Ereignishandler im Seitenlebenszyklus werden weiterhin ausgeführt. (Wenn Sie beispielsweise die Umleitung in Page Load ausführen, wird nicht nur der Rest des Handlers ausgeführt, sondern auch Page_PreRender usw. wird weiterhin aufgerufen. Die gerenderte Seite wird nur nicht an den Browser gesendet. Sie können die zusätzliche Verarbeitung durch vermeiden Setzen Sie beispielsweise ein Flag auf der Seite und lassen Sie nachfolgende Ereignishandler dieses Flag überprüfen, bevor Sie eine Verarbeitung durchführen.

(In der Dokumentation CompleteRequestheißt es, dass ASP.NET alle Ereignisse und Filter in der Ausführungskette der HTTP-Pipeline umgeht . Dies kann leicht missverstanden werden. Weitere HTTP-Filter und -Module werden umgangen, weitere Ereignisse werden jedoch nicht umgangen in der aktuellen Seite Lebenszyklus.)

Das tiefere Problem ist, dass WebForms keine Abstraktionsebene aufweist. Wenn Sie sich in einem Ereignishandler befinden, sind Sie bereits dabei, eine Seite für die Ausgabe zu erstellen. Das Umleiten in einem Ereignishandler ist hässlich, da Sie eine teilweise generierte Seite beenden, um eine andere Seite zu generieren. MVC hat dieses Problem nicht, da der Steuerungsfluss vom Rendern von Ansichten getrennt ist. Sie können also eine saubere Umleitung durchführen, indem Sie einfach eine RedirectActionim Controller zurückgeben, ohne eine Ansicht zu generieren.

JacquesB
quelle
7
Ich glaube, die beste Beschreibung von Webformularen, die ich je gehört habe, war "Lügensauce".
McFea
9
Ich liebe die Detailgenauigkeit dieser Antwort. Besser als die akzeptierte Antwort
Jess
Wenn Sie die Dirty- Option verwenden, können Sie die Unterbrechung von ThreadAbortException in Visual Studio deaktivieren. DEBUG> Ausnahmen ... . Erweitern Sie CLR> System.Threading> Deaktivieren Sie System.Threading.ThreadAbortException .
Jess
Gott sei Dank haben wir jemanden mit der richtigen Antwort, und dies sollte die Antwort mit der höchsten Stimme sein.
Abs
1
In meinem Fall tritt diese Ausnahme nicht jedes Mal auf, sondern nur einige Male dazwischen. Bedeutet Wenn und Klicken auf dieselbe Schaltfläche der Live-Anwendung es funktioniert, aber wenn auf denselben Link und dieselbe Schaltfläche von einem anderen Computer aus geklickt wird, wird System.Threading.ThreadAbortException ausgegeben. Irgendeine Idee, warum es nicht jedes Mal passiert?
Sagar Shirke
33

Ich weiß, dass ich zu spät bin, aber ich habe diesen Fehler immer nur gehabt, wenn ich Response.Redirectin einem Try...CatchBlock bin .

Fügen Sie niemals eine Response.Redirect in einen Try ... Catch-Block ein. Es ist eine schlechte Praxis

Bearbeiten

Als Antwort auf den Kommentar von @ Kiquenet würde ich Folgendes als Alternative zum Einfügen von Response.Redirect in den Block Try ... Catch tun.

Ich würde die Methode / Funktion in zwei Schritte aufteilen.

Schritt eins im Block Try ... Catch führt die angeforderten Aktionen aus und legt einen "Ergebnis" -Wert fest, um den Erfolg oder Misserfolg der Aktionen anzuzeigen.

Schritt zwei außerhalb des Try ... Catch-Blocks führt die Umleitung durch (oder nicht), je nachdem, wie hoch der Wert "result" ist.

Dieser Code ist alles andere als perfekt und sollte wahrscheinlich nicht kopiert werden, da ich ihn nicht getestet habe

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
quelle
@Kiquenet In meiner aktualisierten Antwort finden Sie ein Beispiel dafür, was ich tun würde. Um nicht zu sagen, es ist der beste Kurs, aber ich denke, es ist eine praktikable Alternative.
Ortund
Ich hatte das Problem erst, als ich meinen Code in einen Versuch eingewickelt habe. Ich frage mich, welche anderen Codeaufrufe dieses Verhalten in .NET verursachen
interessanter Name hier
8

Response.Redirect() löst eine Ausnahme aus, um die aktuelle Anforderung abzubrechen.

Dieser KB-Artikel beschreibt dieses Verhalten (auch für die Methoden Request.End()und Server.Transfer()).

Denn Response.Redirect()es gibt eine Überlastung:

Response.Redirect(String url, bool endResponse)

Wenn Sie endResponse = false übergeben , wird die Ausnahme nicht ausgelöst (die Laufzeit verarbeitet jedoch weiterhin die aktuelle Anforderung).

Wenn endResponse = true ist (oder wenn die andere Überladung verwendet wird), wird die Ausnahme ausgelöst und die aktuelle Anforderung wird sofort beendet.

M4N
quelle
7

Hier ist die offizielle Zeile zum Problem (ich konnte die neueste nicht finden, aber ich glaube nicht, dass sich die Situation für spätere Versionen von .net geändert hat).

Spender
quelle
5
@svick Unabhängig von Link Rot sind nur Link-Antworten keine wirklich guten Antworten. 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

So Response.Redirect(url, true) funktioniert es. Es wirft das ThreadAbortException, um den Thread abzubrechen. Ignorieren Sie einfach diese Ausnahme. (Ich nehme an, es ist ein globaler Fehlerbehandler / Logger, wo Sie es sehen?)

Eine interessante verwandte Diskussion wird Response.End()als schädlich angesehen? .

Martin Smith
quelle
4
Das Abbrechen eines Threads scheint ein sehr hartnäckiger Weg zu sein, um mit dem vorzeitigen Ende der Reaktion umzugehen. Ich finde es seltsam, dass das Framework es nicht vorziehen würde, den Thread wiederzuverwenden, anstatt einen neuen zu drehen, um seinen Platz einzunehmen.
Spender
3

Ich habe auch eine andere Lösung ausprobiert, aber ein Teil des Codes wurde nach der Umleitung ausgeführt.

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

Wenn Sie also die Codeausführung nach der Umleitung verhindern müssen

try
{
   //other code
   Response.Redirect("")
  // code not to be executed
}
catch(ThreadAbortException){}//do there id nothing here
catch(Exception ex)
{
  //Logging
}
Maxim Lawrow
quelle
1
Folgen Sie einfach der Antwort von Jorge. Dadurch wird die Protokollierung der Thread-Abbruch-Ausnahme effektiv entfernt.
Maxim Lawrow
Wenn jemand fragt, warum er eine Ausnahme bekommt, ist es keine Antwort, ihm zu sagen, er solle nur mit try..catch spielen. Siehe die akzeptierte Antwort. Ich habe Ihre Antwort kommentiert, als ich "Late Answer"
manuell
Dies hat den gleichen Effekt wie das Setzen von false für das zweite Argument von Response.Redirect, aber "false" ist eine bessere Lösung als das Erfassen der ThreadAbortException. Ich sehe nicht, dass es jemals einen guten Grund gibt, dies so zu tun.
NickG
2

Ich habe sogar versucht, dies zu vermeiden, nur für den Fall, dass der Abbruch des Threads manuell ausgeführt wird, aber ich lasse ihn lieber bei "CompleteRequest" und gehe weiter - mein Code hat ohnehin Rückbefehle nach Umleitungen. Das kann also gemacht werden

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

Was ich tue, ist diese Ausnahme zusammen mit anderen möglichen Ausnahmen zu fangen. Hoffe das hilft jemandem.

 catch (ThreadAbortException ex1)
 {
    writeToLog(ex1.Message);
 }
 catch(Exception ex)
 {
     writeToLog(ex.Message);
 }
Jorge
quelle
2
Vermeiden Sie besser eine ThreadAbortException- Ausnahme als fangen und nichts tun ?
Kiquenet
-1

Ich hatte auch dieses Problem.

Versuchen Sie es mit Server.Transferanstelle vonResponse.Redirect

Hat für mich gearbeitet.

Marko
quelle
2
Server.Transfer sollte weiterhin eine ThreadAbortException auslösen: support.microsoft.com/kb/312629 , daher ist dies keine empfohlene Lösung.
Joel Beckham
9
Server.Transfer sendet keine Weiterleitung an den Benutzer. Es hat einen ganz anderen Zweck!
Marcel
1
Server.transfer und responce.redirect sind unterschiedlich
mzonerz