Почему Response.Redirect вызывает System.Threading.ThreadAbortException?

230

Когда я использую Response.Redirect (...) для перенаправления моей формы на новую страницу, я получаю сообщение об ошибке:

Первое исключение типа «System.Threading.ThreadAbortException» возникло в mscorlib.dll
. Исключение типа «System.Threading.ThreadAbortException» произошло в mscorlib.dll, но не было обработано в коде пользователя.

Насколько я понимаю, эта ошибка вызвана тем, что веб-сервер прерывает оставшуюся часть страницы, на которой был вызван response.redirect.

Я знаю, что могу добавить второй параметр, Response.Redirectкоторый называется endResponse. Если для параметра endResponse установлено значение True, я все равно получаю сообщение об ошибке, но если установить значение False, то нет. Я почти уверен, что это означает, что веб-сервер запускает остальную часть страницы, с которой я перенаправлен. Что может показаться неэффективным, если не сказать больше. Есть лучший способ сделать это? Что-то кроме Response.Redirectили есть способ заставить старую страницу перестать загружаться, где я не получу ThreadAbortException?

Бен Хоффман
источник

Ответы:

332

Правильный шаблон - вызвать перегрузку Redirect с endResponse = false и выполнить вызов, чтобы сообщить конвейеру IIS, что он должен перейти непосредственно к этапу EndRequest после возврата элемента управления:

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

В этом блоге Томаса Марквардта содержатся дополнительные сведения, в том числе о том, как обрабатывать особый случай перенаправления внутри обработчика Application_Error.

Джоэл Филлмор
источник
6
Он выполняет код после Context.ApplicationInstance.CompleteRequest();. Зачем? Придется ли мне returnиз обработчика событий условно?
IsmailS
4
@Ismail: старая версия Redirect создает исключение ThreadAbortException для предотвращения выполнения любого последующего кода. Более новая, предпочтительная версия не выдает, но вы отвечаете за ранний возврат управления, если у вас есть дополнительный код в обработчике.
Джоэл Филлмор
12
Я думаю, что правильнее будет сказать «вторая перегрузка», а не The old version of Redirectфразу, которую вы используете в своем комментарии, это не значит, что MS изменила реализацию, это просто очередная перегрузка.
BornToCode
2
Я не думаю, что это идеальный образец. Вы просите страницу не завершать ответ и продолжить выполнение, а затем завершить запрос программным способом. Но как насчет рендеринга aspx-страницы и обработчиков событий? не завершение ответа означает, что он завершит рендеринг страницы aspx перед нажатием «completeRequest ()». Теперь, если я использую свойство серверной стороны на своей странице, скажите переменную сеанса, чтобы определить действительный вход в систему, который, если истекает, выдаст нулевое исключение даже перед перенаправлением. И единственный способ исправить это - вернуть endResponse в true.
Абс
1
Собирался проголосовать за этот ответ, но код этой страницы продолжал выполняться. Это не идеально в моем случае. Намного чище обрабатывать или игнорировать «ThreadAbortException»
DaniDev
159

В ASP.Net WebForms нет простого и элегантного решения Redirectпроблемы. Вы можете выбрать между грязным решением и утомительным решением

Грязный : Response.Redirect(url)отправляет перенаправление в браузер, а затем выбрасывает, ThreadAbortedExceptionчтобы завершить текущий поток. Таким образом, никакой код не выполняется после вызова Redirect (). Недостатки: это плохая практика, и это может повлиять на производительность для уничтожения подобных потоков. Кроме того, ThreadAbortedExceptionsбудет отображаться в журнале исключений.

Утомительно : рекомендуемый способ - вызвать, Response.Redirect(url, false)а затем, Context.ApplicationInstance.CompleteRequest()однако, выполнение кода будет продолжено, а остальные обработчики событий в жизненном цикле страницы будут по-прежнему выполняться. (Например, если вы выполняете перенаправление в Page_Load, не только будет выполняться остальная часть обработчика, также будет вызываться Page_PreRender и т. Д. - отображаемая страница просто не будет отправлена ​​в браузер. Вы можете избежать дополнительной обработки, выполнив например, установить флаг на странице, а затем позволить последующим обработчикам событий проверить этот флаг перед выполнением какой-либо обработки.

(В документации CompleteRequestговорится, что он « заставляет ASP.NET обходить все события и фильтрацию в цепочке выполнения конвейера HTTP ». Это может быть неправильно понято. Он обходит другие фильтры и модули HTTP, но не обходит дальнейшие события в текущем жизненном цикле страницы .)

Более глубокая проблема заключается в том, что WebForms не хватает уровня абстракции. Когда вы находитесь в обработчике событий, вы уже находитесь в процессе создания страницы для вывода. Перенаправление в обработчике событий ужасно, потому что вы закрываете частично сгенерированную страницу, чтобы создать другую страницу. MVC не имеет этой проблемы, поскольку поток управления отделен от представлений рендеринга, поэтому вы можете выполнить чистое перенаправление, просто возвращая RedirectActionв контроллере, не генерируя представление.

JacquesB
источник
7
Я считаю, что лучшим описанием веб-форм, которые я когда-либо слышал, было «соус для лжи».
McFEA
9
Я люблю количество деталей в этом ответе. Лучше, чем принятый ответ
Джесс
Если вы используете грязную опцию, вы можете отключить разрыв на ThreadAbortException в Visual Studio. DEBUG> Исключения ... . Разверните CLR> System.Threading> Снимите флажок System.Threading.ThreadAbortException .
Джесс
слава богу, у нас есть кто-то с правильным ответом, и это должен быть самый высокий голос.
Абс
1
В моем случае это исключение происходит не каждый раз, а только в течение нескольких раз. Означает Если и, нажимая на ту же кнопку приложения Live, оно работает, но при нажатии той же ссылки и той же кнопки с другого компьютера, это дает System.Threading.ThreadAbortException. Есть идеи, почему это не происходит каждый раз?
Сагар Ширке
34

Я знаю, что опоздал, но у меня когда-либо была эта ошибка, только если я Response.Redirectв Try...Catchблоке.

Никогда не помещайте Response.Redirect в блок Try ... Catch. Это плохая практика

редактировать

В ответ на комментарий @ Kiquenet вот что я бы сделал в качестве альтернативы помещению Response.Redirect в блок Try ... Catch.

Я бы разбил метод / функцию на два этапа.

Первый шаг внутри блока Try ... Catch выполняет запрошенные действия и устанавливает значение «result», указывающее на успех или неудачу действий.

Шаг два за пределами блока Try ... Catch выполняет перенаправление (или не делает) в зависимости от значения «result».

Этот код далек от совершенства и, вероятно, его не следует копировать, поскольку я его не проверял

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
источник
@Kiquenet, пожалуйста, смотрите мой обновленный ответ для примера того, что я буду делать. Не сказать, что это лучший курс, но я думаю, что это жизнеспособная альтернатива.
Ортунд
У меня не было проблемы, пока я не завернул свой код в попытке, поймать ... Интересно, какие другие вызовы кода вызывают такое поведение в .NET
интересное имя здесь
8

Response.Redirect() выдает исключение для отмены текущего запроса.

Это KB статья описывает это поведение ( в том числе для Request.End()иServer.Transfer() методов).

Ибо Response.Redirect()существует перегрузка:

Response.Redirect(String url, bool endResponse)

Если вы передаете endResponse = false , то исключение не (но среда выполнения продолжит обработку текущего запроса).

Если endResponse = true (или если используется другая перегрузка), генерируется исключение, и текущий запрос будет немедленно прекращен.

M4N
источник
7

Вот официальная строка по проблеме (я не смог найти последнюю версию, но я не думаю, что ситуация изменилась для более поздних версий .net)

расточитель
источник
5
@svick Независимо от гнили ссылок, ответы только по ссылкам - не очень хорошие ответы. meta.stackexchange.com/q/8231 I think that links are fantastic, but they should never be the only piece of information in your answer.
Райан Гейтс,
7

Это просто как Response.Redirect(url, true) работает. Это бросает, ThreadAbortExceptionчтобы прервать поток. Просто игнорируйте это исключение. (Я предполагаю, что это какой-то глобальный обработчик ошибок / регистратор, где вы его видите?)

Интересная связанная дискуссия считается ли Response.End()вредной? ,

Мартин Смит
источник
4
Прекращение потока кажется действительно тяжелым способом справиться с преждевременным окончанием ответа. Мне кажется странным, что фреймворк не предпочел бы повторно использовать поток вместо того, чтобы раскручивать новый, чтобы занять его место.
спонсор
3

Также я попробовал другое решение, но некоторые из кода выполняются после перенаправления.

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
}
Максим Лавров
источник
1
просто ответьте ответом Хорхе. Это фактически удалит запись исключения прерывания потока.
Максим Лавров
Когда кто-то спрашивает, почему он получает Исключение, он говорит ему просто поиграть с try..catch - это не ответ. Смотрите принятый ответ. Я прокомментировал ваш ответ при рассмотрении «позднего ответа»
manuell
Это имеет тот же эффект, что и установка false для 2-го аргумента Response.Redirect, но «false» является более хорошим решением, чем захват ThreadAbortException. Я не вижу, что когда-либо есть веская причина сделать это таким образом.
NickG
2

я даже пытался избежать этого, на всякий случай делая прерывание в потоке вручную, но я предпочитаю оставить его с «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();
}
SammuelMiranda
источник
1

Что я делаю, так это ловлю это исключение вместе с другими возможными исключениями. Надеюсь, это поможет кому-то.

 catch (ThreadAbortException ex1)
 {
    writeToLog(ex1.Message);
 }
 catch(Exception ex)
 {
     writeToLog(ex.Message);
 }
Jorge
источник
2
Лучше избегать исключения ThreadAbortException, чем catch и ничего не делать ?
Kiquenet
-1

У меня тоже была эта проблема.

Попробуйте использовать Server.TransferвместоResponse.Redirect

Работал на меня.

Marko
источник
2
Server.Transfer должен по-прежнему генерировать исключение ThreadAbortException: support.microsoft.com/kb/312629 , поэтому это не рекомендуемое решение.
Джоэл Бекхэм
9
Server.Transfer не будет отправлять перенаправление пользователю. У него совсем другая цель!
Марсель
1
Server.transfer и Responce.redirect разные
mzonerz