Mengapa Response.Redirect menyebabkan System.Threading.ThreadAbortException?

230

Ketika saya menggunakan Response.Redirect (...) untuk mengarahkan ulang formulir saya ke halaman baru, saya mendapatkan kesalahan:

Pengecualian kesempatan pertama dari tipe 'System.Threading.ThreadAbortException' terjadi di mscorlib.dll
. Pengecualian dari tipe 'System.Threading.ThreadAbortException' terjadi di mscorlib.dll tetapi tidak ditangani dalam kode pengguna

Pemahaman saya tentang hal ini adalah bahwa kesalahan disebabkan oleh server web membatalkan sisa halaman response.redirect dipanggil.

Saya tahu saya bisa menambahkan parameter kedua ke Response.Redirectyang disebut endResponse. Jika saya mengatur endResponse ke True saya masih mendapatkan kesalahan tetapi jika saya mengaturnya ke False maka saya tidak melakukannya. Saya cukup yakin meskipun itu berarti server web menjalankan sisa halaman yang saya arahkan kembali. Yang tampaknya tidak efisien untuk sedikitnya. Apakah ada cara yang lebih baik untuk melakukan ini? Sesuatu selainResponse.Redirect atau apakah ada cara untuk memaksa halaman lama berhenti memuat di mana saya tidak akan mendapatkan ThreadAbortException?

Ben Hoffman
sumber

Jawaban:

332

Pola yang benar adalah dengan memanggil Redirect overload dengan endResponse = false dan membuat panggilan untuk memberi tahu pipa IIS bahwa itu harus maju langsung ke tahap EndRequest setelah Anda mengembalikan kontrol:

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

Posting blog ini dari Thomas Marquardt memberikan detail tambahan, termasuk bagaimana menangani kasus khusus pengalihan di dalam Application_Error handler.

Joel Fillmore
sumber
6
Ini mengeksekusi kode setelah Context.ApplicationInstance.CompleteRequest();. Mengapa? Apakah saya harus returndari pengatur acara bersyarat?
IsmailS
4
@Ismail: Versi lama Redirect melempar ThreadAbortException untuk mencegah eksekusi kode berikutnya. Versi yang lebih baru dan lebih disukai tidak melempar, tetapi Anda bertanggung jawab untuk mengembalikan kontrol lebih awal jika Anda memiliki kode tambahan di handler.
Joel Fillmore
12
Saya pikir itu lebih akurat untuk mengatakan "kelebihan kedua" daripada The old version of Redirectfrasa yang Anda gunakan dalam komentar Anda, tidak seperti MS mengubah implementasinya, itu hanya kelebihan lain.
BornToCode
2
Saya tidak berpikir ini adalah pola yang ideal. Anda meminta halaman untuk tidak mengakhiri respons dan melanjutkan eksekusi dan kemudian menyelesaikan permintaan secara terprogram. Tapi bagaimana dengan rendering halaman aspx dan event handler? tidak mengakhiri respons berarti, itu akan selesai merender halaman aspx sebelum menekan "completeRequest ()". Sekarang jika saya menggunakan properti sisi server di halaman saya katakan variabel sesi untuk menentukan login yang valid, yang jika kedaluwarsa akan membuang pengecualian nol bahkan sebelum mengarahkan ulang. Dan satu-satunya cara untuk memperbaikinya adalah membuat endResponse kembali ke true.
Abs
1
Akan memilih suara ini jawaban, tetapi kode halaman itu terus mengeksekusi. Ini tidak ideal dalam kasus saya. Jauh lebih bersih untuk menangani atau mengabaikan "ThreadAbortException"
DaniDev
159

Tidak ada solusi sederhana dan elegan untuk Redirectmasalah di ASP.Net WebForms. Anda dapat memilih antara solusi Kotor dan solusi Tedious

Kotor : Response.Redirect(url)mengirimkan pengalihan ke browser, dan kemudian melempar ThreadAbortedExceptionuntuk mengakhiri utas saat ini. Jadi tidak ada kode yang dieksekusi melewati Redirect () - panggilan. Kelemahan: Ini adalah praktik yang buruk dan memiliki implikasi kinerja untuk membunuh utas seperti ini. Juga, ThreadAbortedExceptionsakan muncul dalam pengecualian logging.

Menjemukan : Cara yang disarankan adalah menelepon Response.Redirect(url, false)dan kemudian Context.ApplicationInstance.CompleteRequest()Namun, eksekusi kode akan berlanjut dan sisa penangan acara di siklus hidup halaman masih akan dieksekusi. (Misalnya, jika Anda melakukan pengalihan di Page_Load, tidak hanya sisa handler akan dieksekusi, Page_PreRender dan seterusnya juga akan dipanggil - halaman yang diberikan tidak akan dikirim ke browser. Anda dapat menghindari pemrosesan tambahan dengan mis. mengatur bendera pada halaman, dan kemudian biarkan penangan acara berikutnya memeriksa bendera ini sebelum sebelum melakukan pemrosesan.

(Dokumentasi CompleteRequestmenyatakan bahwa hal itu " Menyebabkan ASP.NET memintas semua peristiwa dan memfilter dalam rantai eksekusi HTTP ". Ini dapat dengan mudah disalahpahami. Ia mem-bypass filter dan modul HTTP lebih lanjut, tetapi tidak mem-bypass peristiwa lebih lanjut dalam siklus hidup halaman saat ini .)

Masalah yang lebih dalam adalah bahwa WebForms tidak memiliki tingkat abstraksi. Ketika Anda berada dalam event handler, Anda sudah dalam proses membangun halaman untuk menghasilkan. Mengarahkan dalam pengendali acara adalah jelek karena Anda mengakhiri halaman yang dibuat sebagian untuk menghasilkan halaman yang berbeda. MVC tidak memiliki masalah ini karena aliran kontrol terpisah dari tampilan rendering, sehingga Anda dapat melakukan redirect bersih dengan hanya mengembalikan a RedirectActiondi controller, tanpa menghasilkan tampilan.

JacquesB
sumber
7
Saya percaya deskripsi terbaik tentang formulir web yang pernah saya dengar adalah "saus bohong."
mcfea
9
Saya suka jumlah detail dalam jawaban ini. Lebih baik daripada jawaban yang diterima
Jess
Jika Anda menggunakan opsi kotor , Anda bisa mematikan jeda di ThreadAbortException di Visual Studio. DEBUG> Pengecualian ... . Luaskan CLR> System.Threading> Hapus centang System.Threading.ThreadAbortException .
Jess
Terima kasih Tuhan kami memiliki seseorang dengan jawaban yang tepat, dan ini harus menjadi jawaban dengan suara tertinggi.
Abs
1
Dalam kasus saya pengecualian ini tidak datang untuk setiap waktu, hanya untuk beberapa kali di antaranya terjadi. Berarti Jika dan mengklik tombol yang sama dari aplikasi Live itu berfungsi tetapi ketika tautan yang sama dan tombol yang sama diklik dari mesin lain itu memberikan System.Threading.ThreadAbortException. Adakah yang tahu mengapa itu tidak terjadi setiap saat ??
Sagar Shirke
33

Saya tahu saya terlambat, tetapi saya hanya pernah memiliki kesalahan ini jika saya Response.Redirectada di Try...Catchblok.

Jangan pernah berikan Response.Redirect ke blok Coba ... Tangkap. Ini praktik buruk

Edit

Menanggapi komentar @ Kiquenet, inilah yang akan saya lakukan sebagai alternatif untuk meletakkan Response.Redirect ke blok Try ... Catch.

Saya akan memecah metode / fungsi menjadi dua langkah.

Langkah satu di dalam blok Coba ... Tangkap melakukan tindakan yang diminta dan menetapkan nilai "hasil" untuk menunjukkan keberhasilan atau kegagalan tindakan.

Langkah dua di luar blok Coba ... Tangkapan melakukan pengalihan (atau tidak) tergantung pada apa nilai "hasil".

Kode ini jauh dari sempurna dan mungkin tidak boleh disalin karena saya belum mengujinya

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
sumber
@Kiquenet, silakan lihat jawaban saya yang diperbarui untuk contoh apa yang akan saya lakukan. Bukan untuk mengatakan itu jalan terbaik, tapi itu adalah alternatif yang layak menurut saya.
Ortund
Tidak memiliki masalah sampai aku membungkus kode saya di mencoba, menangkap ... Aku ingin tahu apa panggilan kode lain menyebabkan perilaku ini di NET
menarik-nama-sini
8

Response.Redirect() melempar pengecualian untuk membatalkan permintaan saat ini.

Ini artikel KB menjelaskan perilaku ini (juga untuk Request.End()dan Server.Transfer()metode).

Karena Response.Redirect()ada kelebihan:

Response.Redirect(String url, bool endResponse)

Jika Anda meneruskan endResponse = false , maka pengecualian tidak dilempar (tetapi runtime akan melanjutkan pemrosesan permintaan saat ini).

Jika endResponse = true (atau jika kelebihan lainnya digunakan), pengecualian dilemparkan dan permintaan saat ini akan segera dihentikan.

M4N
sumber
7

Inilah baris resmi untuk masalah ini (saya tidak dapat menemukan yang terbaru, tetapi saya tidak berpikir situasinya telah berubah untuk versi .net yang lebih baru)

pemboros
sumber
5
@vick Terlepas dari kebusukan tautan, hanya tautan jawaban yang bukan jawaban yang benar-benar hebat. 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

Beginilah cara Response.Redirect(url, true) kerjanya. Itu melempar ThreadAbortExceptionuntuk membatalkan utas. Abaikan saja pengecualian itu. (Saya kira itu adalah beberapa penangan kesalahan global / logger di mana Anda melihatnya?)

Diskusi terkait yang menarik Apakah Response.End()Dianggap Berbahaya? .

Martin Smith
sumber
4
Membatalkan sebuah utas sepertinya merupakan cara yang sangat berat untuk menghadapi akhir tanggapan yang prematur. Saya merasa aneh bahwa framework tidak akan memilih untuk menggunakan kembali thread daripada memutar yang baru untuk menggantikannya.
pemboros
3

Saya juga mencoba solusi lain, tetapi beberapa kode dieksekusi setelah redirect.

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

Jadi jika perlu mencegah eksekusi kode setelah redirect

try
{
   //other code
   Response.Redirect("")
  // code not to be executed
}
catch(ThreadAbortException){}//do there id nothing here
catch(Exception ex)
{
  //Logging
}
Maxim Lavrov
sumber
1
ikuti saja jawaban Jorge. Ini akan secara permanen menghapus logging dari pengecualian Thread dibatalkan.
Maxim Lavrov
Ketika seseorang bertanya mengapa dia mendapat Exception, katakan padanya untuk hanya bermain dengan try..catch bukan jawaban. Lihat jawaban yang diterima. Saya mengomentari jawaban Anda saat meninjau "jawaban terlambat"
manuell
Itu memiliki efek yang sama dengan menempatkan false untuk argumen 2 dari Response.Redirect, tetapi "false" adalah solusi yang lebih baik daripada menangkap ThreadAbortException. Saya tidak melihat bahwa ada alasan bagus untuk melakukannya dengan cara ini.
NickG
2

saya bahkan mencoba untuk menghindari ini, kalau-kalau melakukan Abort pada utas secara manual, tetapi saya lebih suka meninggalkannya dengan "CompleteRequest" dan melanjutkan - kode saya telah mengembalikan perintah setelah pengalihan. Jadi ini bisa dilakukan

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
sumber
1

Apa yang saya lakukan adalah menangkap pengecualian ini, bersama dengan kemungkinan pengecualian lain. Semoga ini bisa membantu seseorang.

 catch (ThreadAbortException ex1)
 {
    writeToLog(ex1.Message);
 }
 catch(Exception ex)
 {
     writeToLog(ex.Message);
 }
Jorge
sumber
2
Lebih baik menghindari pengecualian ThreadAbortException daripada menangkap dan tidak melakukan apa pun ?
Kiquenet
-1

Saya juga punya masalah itu.

Coba gunakan Server.Transfersebagai gantiResponse.Redirect

Bekerja untukku.

Marko
sumber
2
Server.Transfer masih harus melempar ThreadAbortException: support.microsoft.com/kb/312629 , jadi itu bukan solusi yang disarankan.
Joel Beckham
9
Server.Transfer tidak akan mengirim pengalihan ke pengguna. Ini memiliki tujuan yang berbeda sama sekali!
Marcel
1
Server.transfer dan responce.redirect berbeda
mzonerz