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

223

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

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

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

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

Теги:
.net-3.5

10 ответов

325
Лучший ответ

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

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

Это сообщение в блоге от Thomas Marquardt содержит дополнительные сведения, в том числе о том, как обрабатывать специальный случай перенаправления внутри обработчика Application_Error.

  • 6
    Он выполняет код после Context.ApplicationInstance.CompleteRequest(); , Зачем? Придется ли мне return из обработчика событий условно?
  • 4
    @Ismail: старая версия Redirect генерирует исключение ThreadAbortException для предотвращения выполнения любого последующего кода. Более новая, предпочтительная версия не выдает, но вы несете ответственность за ранний возврат управления, если у вас есть дополнительный код в обработчике.
Показать ещё 5 комментариев
144

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

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

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

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

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

  • 2
    Bleurg ... это только одна из причин, почему веб-формы уродливы ...
  • 5
    Я считаю, что лучшим описанием веб-форм, которое я когда-либо слышал, было «соус для лжи».
Показать ещё 4 комментария
28

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

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

Изменить

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

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

Шаг первый внутри блока Try... Catch выполняет запрошенные действия и устанавливает значение "результат" для указания успеха или отказа действий.

Шаг второй за пределами поля "Try... Catch" выполняет перенаправление (или не входит) в зависимости от того, что такое "результат".

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

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.
    }
}
  • 1
    Какова хорошая модель и практика ?
  • 0
    @Kiquenet, пожалуйста, смотрите мой обновленный ответ для примера того, что я буду делать. Не сказать, что это лучший курс, но я думаю, что это жизнеспособная альтернатива.
Показать ещё 1 комментарий
8

Response.Redirect() выдает исключение, чтобы прервать текущий запрос.

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

Для Response.Redirect() существует перегрузка:

Response.Redirect(String url, bool endResponse)

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

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

7

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

Интересное связанное обсуждение Является ли Response.End() считающимся вредным?

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

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

  • 4
    @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.
2

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

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

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
}
  • 0
    просто ответьте ответом Хорхе. Это фактически удалит запись исключения прерывания потока.
  • 0
    Когда кто-то спрашивает, почему он получает исключение, он говорит ему просто поиграть с try..catch - это не ответ. Смотрите принятый ответ. Я прокомментировал ваш ответ при просмотре "позднего ответа"
Показать ещё 1 комментарий
1

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

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

У меня тоже была эта проблема. Попробуйте использовать Server.Transfer вместо Response.Redirect Работал для меня

  • 2
    Server.Transfer должен по-прежнему генерировать исключение ThreadAbortException: support.microsoft.com/kb/312629 , поэтому это не рекомендуемое решение.
  • 8
    Server.Transfer не будет отправлять перенаправление пользователю. У него совсем другая цель!
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню