ทำไม Response.Redirect ทำให้เกิด System.Threading.ThreadAbortException


230

เมื่อฉันใช้ Response.Redirect (... ) เพื่อเปลี่ยนเส้นทางแบบฟอร์มของฉันไปยังหน้าใหม่ฉันได้รับข้อผิดพลาด:

มีโอกาสเกิดข้อยกเว้นครั้งแรกของประเภท 'System.Threading.ThreadAbortException' เกิดขึ้นใน mscorlib.dll
ข้อยกเว้นประเภท 'System.Threading.Thread.ThreadAbortException' เกิดขึ้นใน mscorlib.dll แต่ไม่ได้รับการจัดการในรหัสผู้ใช้

ความเข้าใจของฉันเกี่ยวกับเรื่องนี้คือข้อผิดพลาดที่เกิดจากเว็บเซิร์ฟเวอร์ยกเลิกส่วนที่เหลือของหน้าเว็บที่มีการตอบสนอง

ฉันรู้ว่าฉันสามารถเพิ่มพารามิเตอร์ตัวที่สองให้กับResponse.Redirectที่เรียกว่า endResponse หากฉันตั้งค่า endResponse เป็น True ฉันยังคงได้รับข้อผิดพลาด แต่ถ้าฉันตั้งค่าเป็น False ฉันจะไม่ทำเช่นนั้น ฉันค่อนข้างแน่ใจว่านั่นหมายความว่าเว็บเซิร์ฟเวอร์กำลังเรียกใช้ส่วนที่เหลือของหน้าที่ฉันเปลี่ยนเส้นทางไปจาก ซึ่งดูเหมือนจะไม่มีประสิทธิภาพที่จะพูดน้อย มีวิธีที่ดีกว่าในการทำเช่นนี้? มีวิธีอื่นนอกเหนือจากResponse.Redirectหรือมีวิธีการบังคับให้หน้าเก่าหยุดการโหลดที่ฉันจะไม่ได้รับThreadAbortExceptionหรือไม่

คำตอบ:


332

รูปแบบที่ถูกต้องคือการเรียกใช้ Redirect overload ด้วย endResponse = false และโทรไปบอก IIS ไปป์ไลน์ว่าควรไปที่ขั้นตอนโดยตรงกับ EndRequest เมื่อคุณกลับมาควบคุม:

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

บล็อกโพสต์นี้จาก Thomas Marquardt ให้รายละเอียดเพิ่มเติมรวมถึงวิธีจัดการกรณีพิเศษของการเปลี่ยนเส้นทางภายในตัวจัดการ Application_Error


6
Context.ApplicationInstance.CompleteRequest();มันรันรหัสหลัง ทำไม? ฉันจะต้องทำreturnจากตัวจัดการเหตุการณ์อย่างมีเงื่อนไขหรือไม่
IsmailS

4
@Ismail: Redirect เวอร์ชันเก่าจะโยน ThreadAbortException เพื่อป้องกันการเรียกใช้โค้ดที่ตามมา เวอร์ชันที่ใหม่กว่าและไม่เป็นที่ต้องการ แต่คุณต้องรับผิดชอบในการคืนการควบคุมก่อนหากคุณมีรหัสเพิ่มเติมในตัวจัดการ
Joel Fillmore

12
ฉันคิดว่ามันแม่นยำกว่าที่จะพูดว่า "การโอเวอร์โหลดครั้งที่สอง" มากกว่าThe old version of Redirectวลีที่คุณใช้ในความคิดเห็นของคุณไม่ใช่ว่า MS เปลี่ยนการใช้งาน แต่เป็นโอเวอร์โหลดอื่น
BornToCode

2
ฉันไม่คิดว่านี่เป็นรูปแบบในอุดมคติ คุณกำลังขอหน้าไม่ให้จบการตอบสนองและดำเนินการต่อจากนั้นดำเนินการตามคำขอโดยทางโปรแกรม แต่สิ่งที่เกี่ยวกับการแสดงผลของหน้า aspx และตัวจัดการเหตุการณ์? ไม่สิ้นสุดการตอบสนองหมายความว่ามันจะเสร็จสิ้นการแสดงผลหน้า aspx ก่อนที่จะกดปุ่ม "completeRequest ()" ตอนนี้ถ้าฉันใช้คุณสมบัติฝั่งเซิร์ฟเวอร์ในหน้าของฉันพูดตัวแปรเซสชันเพื่อตรวจสอบการเข้าสู่ระบบที่ถูกต้องซึ่งถ้าหมดอายุจะโยนข้อยกเว้นโมฆะก่อนที่จะเปลี่ยนเส้นทาง และวิธีเดียวในการแก้ไขนั่นคือทำให้การตอบสนองกลับเป็นจริง
Abs

1
กำลังจะโหวตคำตอบนี้ขึ้นมา แต่โค้ดของหน้านั้นยังคงทำงานต่อไป นี่ไม่เหมาะในกรณีของฉัน ทำความสะอาดมากในการจัดการหรือละเว้น "ThreadAbortException"
DaniDev

159

นอกจากนี้ยังไม่มีการแก้ปัญหาที่ง่ายและสง่างามให้กับRedirectปัญหาที่เกิดขึ้นใน ASP.Net WebForms คุณสามารถเลือกระหว่างDirty solution และTedious solution

Dirty : Response.Redirect(url)ส่งการเปลี่ยนเส้นทางไปยังเบราว์เซอร์แล้วพ่น a ThreadAbortedExceptionเพื่อยกเลิกเธรดปัจจุบัน ดังนั้นจึงไม่มีการเรียกใช้รหัสผ่าน Redirect () - call ข้อเสีย: เป็นการปฏิบัติที่ไม่ดีและมีผลกระทบต่อประสิทธิภาพในการฆ่าเธรดเช่นนี้ นอกจากนี้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
Jess

ขอบคุณพระเจ้าที่เรามีคนที่มีคำตอบที่เหมาะสมและนี่ควรเป็นคำตอบที่ได้รับการโหวตสูงสุด
Abs

1
ในกรณีของฉันข้อยกเว้นนี้ไม่ได้เกิดขึ้นทุกครั้งเพียงไม่กี่ครั้งเท่านั้นที่เกิดขึ้น หมายถึงถ้าและคลิกที่ปุ่มเดียวกันของแอปพลิเคชั่น Live มันทำงานได้ แต่เมื่อลิงค์เดียวกันและปุ่มเดียวกันถูกคลิกจากเครื่องอื่นมันจะให้ระบบ คิดว่าทำไมมันถึงไม่เกิดขึ้นทุกครั้ง ??
Sagar Shirke

33

ฉันรู้ว่าฉันมาสาย แต่ฉันเคยมีข้อผิดพลาดนี้ถ้าฉันResponse.Redirectอยู่ในTry...Catchบล็อก

ไม่ต้องใส่ Response.Redirect ลงใน Try ... Catch block มันเป็นการฝึกฝนที่ไม่ดี

แก้ไข

เพื่อตอบสนองต่อความคิดเห็นของ @ Kiquenet นี่คือสิ่งที่ฉันจะทำเพื่อเป็นทางเลือกในการวาง Response.Redirect ลงใน Try ... Catch block

ฉันจะแบ่งวิธี / ฟังก์ชั่นออกเป็นสองขั้นตอน

ขั้นตอนที่หนึ่งในบล็อก Try ... Catch จะทำการกระทำที่ร้องขอและตั้งค่า "ผลลัพธ์" เพื่อระบุความสำเร็จหรือความล้มเหลวของการกระทำ

ขั้นตอนที่สองที่อยู่นอกบล็อก Try ... Catch block จะเปลี่ยนเส้นทาง (หรือไม่) ขึ้นอยู่กับค่า "ผลลัพธ์"

รหัสนี้ยังห่างไกลจากความสมบูรณ์แบบและอาจไม่ควรคัดลอกเพราะฉันยังไม่ได้ทดสอบ

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 โปรดดูคำตอบที่อัปเดตของฉันสำหรับตัวอย่างของสิ่งที่ฉันจะทำ ไม่ใช่ว่าจะเป็นหลักสูตรที่ดีที่สุด แต่เป็นทางเลือกที่ฉันคิดว่าเป็นไปได้
Ortund

ไม่ได้มีปัญหาจนกว่าฉันจะห่อรหัสของฉันในการลองจับ ... ฉันสงสัยว่าการเรียกรหัสอื่น ๆ ทำให้เกิดพฤติกรรมนี้ใน. 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.
Ryan Gates

7

นี่เป็นวิธีการResponse.Redirect(url, true) ทำงาน มันจะโยนThreadAbortExceptionเพื่อยกเลิกด้าย เพียงละเว้นข้อยกเว้นนั้น (ฉันคิดว่ามันเป็นตัวจัดการข้อผิดพลาดระดับโลก / คนตัดไม้ที่คุณเห็นมัน?)

การอภิปรายที่เกี่ยวข้องที่น่าสนใจคือการResponse.End()พิจารณาอันตราย? .


4
การยกเลิกเธรดดูเหมือนจะเป็นวิธีที่หนักมากในการจัดการกับการตอบสนองก่อนเวลาอันควร ฉันคิดว่ามันแปลกที่เฟรมเวิร์กไม่ต้องการใช้เธรดอีกครั้งแทนที่จะปั่นขึ้นมาใหม่เพื่อใช้แทน
Spender

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
เพียงแค่ติดตามด้วยคำตอบของ Jorge สิ่งนี้จะลบการบันทึกของข้อยกเว้นการยกเลิกเธรดออกไป
Maxim Lavrov

เมื่อมีคนถามว่าทำไมเขาถึงได้รับข้อยกเว้นบอกให้เขาลองเล่นด้วย .. การจับคู่ไม่ใช่คำตอบ ดูคำตอบที่ยอมรับได้ ฉันแสดงความคิดเห็นกับคำตอบของคุณในขณะที่ทบทวน "คำตอบที่ล่าช้า"
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 ข้อยกเว้นกว่าจับและทำอะไร ?
Kiquenet

-1

ฉันมีปัญหานั้นด้วย

ลองใช้Server.TransferแทนResponse.Redirect

ทำงานให้ฉัน


2
Server.Transfer ยังคงควรทิ้ง ThreadAbortException: support.microsoft.com/kb/312629ดังนั้นจึงไม่ใช่วิธีแก้ปัญหาที่แนะนำ
Joel Beckham

9
Server.Transfer จะไม่ส่งการเปลี่ยนเส้นทางไปยังผู้ใช้ มันมีจุดประสงค์ที่ต่างออกไปโดยสิ้นเชิง!
Marcel

1
Server.transfer และ responce.redirect นั้นแตกต่างกัน
mzonerz
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.