Dlaczego Response.Redirect powoduje wyjątek System.Threading.ThreadAbortException?

230

Gdy używam Response.Redirect (...) do przekierowania formularza na nową stronę, pojawia się błąd:

Wystąpił wyjątek pierwszej szansy typu „System.Threading.ThreadAbortException” w mscorlib.dll
Wyjątek typu „System.Threading.ThreadAbortException” wystąpił w mscorlib.dll, ale nie został obsłużony w kodzie użytkownika

Rozumiem to, że błąd jest spowodowany przez serwer internetowy przerywający pozostałą część strony, w której wywołano odpowiedź.redirect.

Wiem, że mogę dodać do tego drugi parametr o Response.Redirectnazwie endResponse. Jeśli ustawię parametr endResponse na wartość True, nadal pojawia się błąd, ale jeśli ustawię wartość False, nie otrzymam. Jestem jednak całkiem pewien, że oznacza to, że serwer internetowy wyświetla resztę strony, z której przekierowałem. Co wydaje się co najmniej nieefektywne. Czy jest na to lepszy sposób? Coś innego niż Response.Redirectlub czy istnieje sposób, aby zmusić starą stronę do zaprzestania ładowania tam, gdzie jej nie dostanę ThreadAbortException?

Ben Hoffman
źródło

Odpowiedzi:

332

Prawidłowym wzorcem jest wywołanie przeciążenia przekierowania za pomocą endResponse = false i wywołanie, aby poinformować potok IIS, że powinien przejść bezpośrednio do etapu EndRequest po przywróceniu kontroli:

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

Ten post na blogu autorstwa Thomasa Marquardta zawiera dodatkowe szczegóły, w tym sposób radzenia sobie ze specjalnym przypadkiem przekierowania wewnątrz modułu obsługi błędu aplikacji.

Joel Fillmore
źródło
6
Wykonuje kod po Context.ApplicationInstance.CompleteRequest();. Czemu? Czy będę musiał returnwarunkowo skorzystać z modułu obsługi zdarzeń?
IsmailS
4
@Ismail: stara wersja przekierowania zgłasza wyjątek ThreadAbortException, aby uniemożliwić wykonanie jakiegokolwiek kolejnego kodu. Nowsza, preferowana wersja nie rzuca, ale jesteś odpowiedzialny za wcześniejsze zwrócenie kontroli, jeśli masz dodatkowy kod w module obsługi.
Joel Fillmore
12
Myślę, że dokładniej jest powiedzieć „drugie przeciążenie” niż The old version of Redirectfrazę, której używasz w komentarzu, to nie tak, że MS zmieniło implementację, to tylko kolejne przeciążenie.
BornToCode,
2
Nie sądzę, że jest to idealny wzór. Pytasz stronę, aby nie kończyła odpowiedzi i kontynuowała wykonywanie, a następnie programowo wypełnia żądanie. Ale co z renderowaniem stron aspx i procedur obsługi zdarzeń? nie kończąc odpowiedzi oznacza to, że zakończy renderowanie strony aspx przed naciśnięciem „completeRequest ()”. Teraz, jeśli korzystam z właściwości po stronie serwera na mojej stronie, powiedz zmienną sesji, aby ustalić poprawny login, który jeśli wygasnie, wyrzuci zerowy wyjątek przed nawet przekierowaniem. Jedynym sposobem, aby to naprawić, jest przywrócenie wartości parametru endResponse.
Abs
1
Zamierzałem zagłosować na tę odpowiedź, ale ten kod strony nadal był wykonywany. W moim przypadku nie jest to idealne. Znacznie czystszy w obsłudze lub ignorowaniu „ThreadAbortException”
DaniDev
159

W ASP.Net WebForms nie ma prostego i eleganckiego rozwiązania tego Redirectproblemu. Możesz wybierać między brudnym rozwiązaniem a żmudnym rozwiązaniem

Brudny : Response.Redirect(url)wysyła przekierowanie do przeglądarki, a następnie wyrzuca a, ThreadAbortedExceptionaby zakończyć bieżący wątek. Zatem żaden kod nie jest wykonywany po wywołaniu Redirect (). Wady: to zła praktyka i mieć wpływ na wydajność, aby zabijać takie wątki. Ponadto ThreadAbortedExceptionspojawi się w logowaniu wyjątków.

Nużący : Zalecanym sposobem jest wywołanie, Response.Redirect(url, false)a następnie Context.ApplicationInstance.CompleteRequest()wykonanie kodu będzie kontynuowane, a pozostałe procedury obsługi zdarzeń w cyklu życia strony będą nadal wykonywane. (Np. Jeśli wykonasz przekierowanie w Page_Load, nie tylko reszta programu obsługi zostanie wykonana, Page_PreRender itd. Nadal będzie wywoływana - renderowana strona po prostu nie zostanie wysłana do przeglądarki. Możesz uniknąć dodatkowego przetwarzania przez np. ustawiając flagę na stronie, a następnie pozwól kolejnym programom obsługi zdarzeń sprawdzić tę flagę przed rozpoczęciem przetwarzania.

(Dokumentacja CompleteRequeststwierdza, że ​​„ powoduje, że ASP.NET pomija wszystkie zdarzenia i filtruje w łańcuchu wykonawczym potoku HTTP ”. Można to łatwo zrozumieć. Pomija kolejne filtry i moduły HTTP, ale nie pomija dalszych zdarzeń w bieżącym cyklu życia strony ).

Głębszy problem polega na tym, że WebFormom brakuje poziomu abstrakcji. Gdy jesteś w module obsługi zdarzeń, jesteś już w trakcie tworzenia strony do wydrukowania. Przekierowywanie w module obsługi zdarzeń jest brzydkie, ponieważ kończysz częściowo wygenerowaną stronę w celu wygenerowania innej strony. MVC nie ma tego problemu, ponieważ przepływ sterowania jest oddzielny od renderowania widoków, więc możesz zrobić czyste przekierowanie po prostu zwracając RedirectActionw kontrolerze, bez generowania widoku.

JacquesB
źródło
7
Uważam, że najlepszym opisem formularzy internetowych, jakie kiedykolwiek słyszałem, był „sos do kłamstwa”.
mcfea
9
Uwielbiam ilość szczegółów w tej odpowiedzi. Lepsza niż zaakceptowana odpowiedź
Jess
Jeśli użyjesz opcji brudnej , możesz wyłączyć przerwanie na ThreadAbortException w Visual Studio. DEBUG> Wyjątki ... . Rozwiń CLR> System.Threading> Odznacz System.Threading.ThreadAbortException .
Jess
dzięki Bogu mamy kogoś z właściwą odpowiedzią i powinna to być najwyższa głosowana odpowiedź.
Abs
1
W moim przypadku ten wyjątek nie pojawia się za każdym razem, tylko kilka razy między nimi. Oznacza to, że kliknięcie tego samego przycisku aplikacji Live działa, ale po kliknięciu tego samego linku i tego samego przycisku na innym komputerze daje to System.Threading.ThreadAbortException. Masz pomysł, dlaczego nie dzieje się za każdym razem?
Sagar Shirke
33

Wiem, że się spóźniam, ale kiedykolwiek miałem ten błąd, jeśli mój Response.Redirectjest w Try...Catchbloku.

Nigdy nie umieszczaj Response.Redirect w bloku Try ... Catch. To zła praktyka

Edytować

W odpowiedzi na komentarz @ Kiquenet, oto co zrobiłbym jako alternatywę dla umieszczenia Response.Redirect w bloku Try ... Catch.

Podzielę metodę / funkcję na dwa etapy.

Krok pierwszy w bloku Try ... Catch wykonuje żądane akcje i ustawia wartość „wynik”, aby wskazać powodzenie lub niepowodzenie akcji.

Krok drugi poza blokiem Try ... Catch dokonuje przekierowania (lub nie) w zależności od wartości „wynik”.

Ten kod jest daleki od ideału i prawdopodobnie nie powinien być kopiowany, ponieważ go nie testowałem

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
źródło
@Kiquenet proszę zobaczyć moją zaktualizowaną odpowiedź na przykład tego, co bym zrobił. Nie mówiąc już, że to najlepszy kurs, ale myślę, że to realna alternatywa.
Ortund
Nie miałem problemu, dopóki nie zapakowałem kodu w próbę, złap ... Zastanawiam się, jakie inne wywołania kodu powodują takie zachowanie w .NET
ciekawe-nazwa-tutaj
8

Response.Redirect() zgłasza wyjątek, aby przerwać bieżące żądanie.

To artykule bazy wiedzy opisano to zachowanie (także dla metod Request.End()i Server.Transfer()).

Dla Response.Redirect() istnieje przeciążenie:

Response.Redirect(String url, bool endResponse)

Jeśli podasz parametr endResponse = false , wyjątek nie zostanie zgłoszony (ale środowisko wykonawcze będzie kontynuowało przetwarzanie bieżącego żądania).

Jeśli endResponse = true (lub jeśli używane jest inne przeciążenie), wyjątek zostanie zgłoszony i bieżące żądanie zostanie natychmiast zakończone.

M4N
źródło
7

Oto oficjalne zdanie na temat problemu (nie mogłem znaleźć najnowszego, ale nie sądzę, że sytuacja uległa zmianie w późniejszych wersjach .net)

wydać
źródło
5
@svick Niezależnie od zgnilizny linków, same odpowiedzi na linki nie są naprawdę świetnymi odpowiedziami. 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

Właśnie tak Response.Redirect(url, true) działa. RzucaThreadAbortException by przerwać wątek. Zignoruj ​​ten wyjątek. (Zakładam, że jest to globalny moduł obsługi / rejestrowania błędów, w którym go widzisz?)

Ciekawa powiązana dyskusja Czy jest Response.End()uważana za szkodliwą? .

Martin Smith
źródło
4
Przerwanie wątku wydaje się być naprawdę ciężkim sposobem radzenia sobie z przedwczesnym zakończeniem reakcji. Dziwne wydaje mi się to, że framework nie wolałby ponownie używać wątku zamiast spinać nowy, aby zajął jego miejsce.
spędzają
3

Próbowałem też innego rozwiązania, ale część kodu została wykonana po przekierowaniu.

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

Więc jeśli chcesz zapobiec wykonaniu kodu po przekierowaniu

try
{
   //other code
   Response.Redirect("")
  // code not to be executed
}
catch(ThreadAbortException){}//do there id nothing here
catch(Exception ex)
{
  //Logging
}
Maksym Ławrow
źródło
1
po prostu odpowiedz na pytanie Jorge. Spowoduje to usunięcie rejestrowania wyjątku przerwania wątku.
Maxim Lavrov
Gdy ktoś pyta, dlaczego dostaje wyjątek, powiedzenie mu, aby po prostu bawił się try. Złapał nie jest odpowiedzią. Zobacz zaakceptowaną odpowiedź. Skomentowałem twoją odpowiedź, recenzując „późną odpowiedź”
manuell
Ma to taki sam efekt, jak wstawienie false dla drugiego argumentu Response.Redirect, ale „false” jest lepszym rozwiązaniem niż przechwycenie wyjątku ThreadAbortException. Nie widzę, aby istniał dobry powód, aby robić to w ten sposób.
NickG
2

nawet próbowałem tego uniknąć, na wszelki wypadek ręcznie przerywając wątek, ale raczej zostawiam to z „CompleteRequest” i kontynuuję - mój kod i tak zwraca polecenia po przekierowaniach. Można to zrobić

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
źródło
1

To, co robię, to złapanie tego wyjątku wraz z innymi możliwymi wyjątkami. Mam nadzieję, że komuś to pomoże.

 catch (ThreadAbortException ex1)
 {
    writeToLog(ex1.Message);
 }
 catch(Exception ex)
 {
     writeToLog(ex.Message);
 }
Jorge
źródło
2
Lepiej unikać wyjątku ThreadAbortException niż łapać i nic nie robić ?
Kiquenet,
-1

Też miałem ten problem.

Spróbuj użyć Server.TransferzamiastResponse.Redirect

Pracował dla mnie.

Marko
źródło
2
Server.Transfer powinien nadal zgłaszać wyjątek ThreadAbortException: support.microsoft.com/kb/312629 , więc nie jest to zalecane rozwiązanie.
Joel Beckham,
9
Server.Transfer nie wyśle ​​przekierowania do użytkownika. Ma zupełnie inny cel!
Marcel
1
Server.transfer i responce.redirect są różne
mzonerz