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

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

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

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

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

11 ответов

Решение

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

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

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

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

Грязный: 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 в контроллере, без генерации представления.

Я знаю, что опоздал, но у меня когда-либо была эта ошибка, только если 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.
    }
}

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

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

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

Response.Redirect(String url, bool endResponse)

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

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

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

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

Интересная связанная с этим дискуссия Является ли Response.End() вредным?

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

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

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
}

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

 catch (ThreadAbortException ex1)
 {
    // do nothing
 }
 catch(Exception ex)
 {
     writeToLog(ex.Message);
 }

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

Сегодня у меня была такая же проблема. В моем проекте у меня было 2 типа страницы журнала (простой пользователь и пользователь-врач). На прошлой неделе я закончил простую страницу входа пользователя, и она работала без каких-либо проблем. На этой неделе я открыл страницу входа доктора. После того, как я его закончил, я снова проверил простой вход пользователя и обнаружил, что он не работает. Я проследил и понял, что проблема в response.redirect("simpleuser.aspx") (не переходите к simpleuser.aspx). Ошибка была (Невозможно оценить выражение, потому что код оптимизирован или собственный фрейм находится поверх стека вызовов.) Я проверил все решения через Интернет, не повезло ... даже "response.redirect(URL,false)", что я сделал, я просто скопировал simpleuser.aspx в другой каталог .... вот и все. Готово. :)

Другие вопросы по тегам