Чому 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

Правильна схема полягає в тому, щоб викликати перенаправлення перенаправлення endResponse = false і здійснити дзвінок, щоб сказати трубопроводу IIS, що він повинен перейти безпосередньо до етапу EndRequest, як тільки ви повернете контроль:

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

Ця публікація в блозі від Thomas Marquardt надає додаткові відомості, зокрема про те, як поводитися з особливим випадком переадресації всередині обробника Application_Error.


6
Він виконує код після Context.ApplicationInstance.CompleteRequest();. Чому? Чи доведеться я returnвід обробника події умовно?
ІсмаїлС

4
@Ismail: Стара версія Redirect передає ThreadAbortException, щоб запобігти виконанню будь-якого наступного коду. Більш нова, бажана версія не кидає, але ви несете відповідальність за повернення контролю на ранньому етапі, якщо у вас є додатковий код в обробнику.
Joel Fillmore

12
Я думаю, точніше сказати "друге перевантаження", а не The old version of Redirectфразу, яку ви використовуєте у своєму коментарі, це не так, як MS змінила реалізацію, це просто чергове перевантаження.
BornToCode

2
Я не думаю, що це ідеальна модель. Ви просите сторінку не закінчувати відповідь і продовжувати виконання, а потім заповнювати запит програмно. Але як щодо рендерингу сторінки aspx та обробників подій? не закінчивши відповідь, це означає, що він завершить надання сторінки aspx, перш ніж натиснути "completeRequest ()". Тепер, якщо я використовую властивість серверної сторінки на своїй сторінці, скажіть змінну сеансу, щоб визначити дійсний логін, який, якщо термін дії закінчиться, видасть нульовий виняток, перш ніж навіть перенаправлення. І єдиний спосіб виправити це - повернути endResponse до справжнього.
Абс

1
Збирався голосувати за цю відповідь, але код цієї сторінки продовжував виконуватись. У моєму випадку це не ідеально. Набагато чистіше обробляти або ігнорувати "ThreadAbortException"
DaniDev

159

У ASP.Net WebForms немає простого і елегантного рішення Redirectпроблеми. Ви можете вибрати між Брудним розчином і утомливих розчином

Брудно : Response.Redirect(url)надсилає переспрямування до браузера, а потім кидає a ThreadAbortedExceptionдля завершення поточного потоку. Тому жоден код не виконується після виклику Redirect (). Недоліки: Це погана практика і мати наслідки для продуктивності, щоб вбити такі теми. Також ThreadAbortedExceptionsбуде показано журнал виключень.

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

(У документації CompleteRequestзазначено, що " Викликає ASP.NET обходити всі події та фільтрацію в ланцюзі виконання конвеєра HTTP ". Це легко зрозуміти неправильно. Він обходить подальші фільтри та модулі HTTP, але не обходить подальші події в поточному життєвому циклі сторінки .)

Більш глибока проблема полягає в тому, що WebForms не має рівня абстракції. Коли ви перебуваєте в обробці подій, ви вже в процесі створення сторінки для виводу. Перенаправлення в обробник подій некрасиво, оскільки ви закінчуєте частково створену сторінку для створення іншої сторінки. У MVC немає цієї проблеми, оскільки потік управління окремий від подання представлень, тому ви можете зробити чисте перенаправлення, просто повернувши a RedirectActionв контролер, не генеруючи представлення.


7
Я вважаю, що найкращий опис веб-форм, які я коли-небудь чув, - це "соус брехня".
mcfea

9
Мені подобається деталь у цій відповіді. Краще за прийняту відповідь
Джесс

Якщо ви використовуєте брудний варіант, ви можете вимкнути перерву на ThreadAbortException у Visual Studio. DEBUG> Винятки ... . Розгорніть CLR> System.Threading> Зніміть прапорець System.Threading.ThreadAbortException .
Джесс

слава богу, у нас є хтось із правильною відповіддю, і це має бути найвищим голосом відповідь.
Абс

1
У моєму випадку цей виняток відбувається не кожен раз, лише кілька разів між ними відбувається. Значить Якщо і натиснувши ту саму кнопку програми Live Live, вона працює, але коли на іншу машину натиснуто те саме посилання та ту саму кнопку, вона дає System.Threading.ThreadAbortException. Будь-яка ідея, чому це не відбувається щоразу ??
Сагар Ширке

33

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

Ніколи не ставити Response.Redirect в блок "Try ... Catch". Це погана практика

Редагувати

У відповідь на коментар @ Kiquenet, ось що я б робив як альтернативу введенню Response.Redirect у блок "Try ... Catch".

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

Крок перший всередині блоку "Спроба ... Спіймати" виконує запитувані дії та встановлює значення "результат", щоб вказати на успіх чи невдачу дій.

Крок другий поза блоком "Спробувати ..." 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.
    }
}

@Kiquenet, будь ласка, дивіться мою оновлену відповідь для прикладу того, що я б робив. Не кажу, що це найкращий курс, але я думаю, що це життєздатна альтернатива.
Ортунд

У мене не було проблеми, поки я не завершив спробу коду, впіймаю ... Цікаво, які ще дзвінки коду викликають таку поведінку в .NET
цікаво-ім'я-тут

8

Response.Redirect() кидає виняток, щоб скасувати поточний запит.

Ця стаття KB описує таку поведінку (також для методів Request.End()та Server.Transfer()).

Оскільки Response.Redirect()існує перевантаження:

Response.Redirect(String url, bool endResponse)

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

Якщо endResponse = true (або використовується інша перевантаження), виняток буде викинуто і поточний запит буде негайно припинено.


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
просто слідкуйте за відповіддю Хорхе. Це фактично видалить журнал виключення Thread abort.
Максим Лавров

Коли хтось запитує, чому він отримує Виняток, то кажіть йому просто пограти з спробувати. Ловитись - це не відповідь. Дивіться прийняту відповідь. Я прокоментував вашу відповідь під час перегляду "пізньої відповіді"
manuell

Це має той самий ефект, що і встановлення false для 2-го аргументу Response.Redirect, але "false" є кращим рішенням, ніж захоплення ThreadAbortException. Я не бачу, що колись є вагомі причини зробити це так.
NickG

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

Те, що я роблю, - це виняток разом із ще одним можливим винятком. Сподіваюся, це допоможе комусь.

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

2
Краще уникати виключення ThreadAbortException, ніж ловити і нічого не робити ?
Кікенет

-1

У мене теж була ця проблема.

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

Працювали для мене.


2
Server.Transfer все ще повинен кидати ThreadAbortException: support.microsoft.com/kb/312629 , тому це не є рекомендованим рішенням.
Джоел Бекхем

9
Server.Transfer не надсилатиме перенаправлення користувачеві. Це зовсім інше призначення!
Марсель

1
Server.transfer і response.redirect різні
mzonerz
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.