C # จับข้อยกเว้นสแตกล้น


116

ฉันมีการเรียกแบบเรียกซ้ำไปยังเมธอดที่ทำให้เกิดข้อยกเว้นสแต็กโอเวอร์โฟลว์ การโทรครั้งแรกล้อมรอบด้วยบล็อก try catch แต่ไม่พบข้อยกเว้น

ข้อยกเว้นของสแตกล้นทำงานในลักษณะพิเศษหรือไม่? ฉันสามารถจับ / จัดการข้อยกเว้นได้อย่างเหมาะสมหรือไม่?

ไม่แน่ใจว่าเกี่ยวข้องหรือไม่ แต่ข้อมูลเพิ่มเติม:

  • ไม่มีข้อยกเว้นในเธรดหลัก

  • วัตถุที่รหัสกำลังส่งข้อยกเว้นจะถูกโหลดด้วยตนเองโดย Assembly.LoadFrom (... ) CreateInstance (... )


3
@RichardOD แน่ใจว่าฉันแก้ไขข้อบกพร่องเพราะมันเป็นจุดบกพร่อง อย่างไรก็ตามปัญหาอาจปรากฏในลักษณะที่แตกต่างออกไปและฉันจะจัดการกับมัน
Toto

7
เห็นด้วยสแตกล้นเป็นข้อผิดพลาดร้ายแรงที่ไม่สามารถจับได้เพราะไม่ควรถูกจับ แก้ไขรหัสเสียแทน
Ian Kemp

11
@RichardOD: ถ้าใครต้องการออกแบบเช่นตัวแยกวิเคราะห์การสืบเชื้อสายซ้ำและไม่กำหนดข้อ จำกัด เทียมเกี่ยวกับความลึกเกินกว่าที่เครื่องโฮสต์ต้องการจริงๆควรทำอย่างไร? ถ้าฉันมี druthers ของฉันจะมีข้อยกเว้น StackCritical ซึ่งสามารถจับได้อย่างชัดเจนซึ่งจะถูกยิงในขณะที่ยังมีพื้นที่สแต็กเหลืออยู่เล็กน้อย มันจะปิดใช้งานตัวเองจนกว่าจะถูกโยนจริงจากนั้นจะไม่สามารถถูกจับได้จนกว่าจะเหลือพื้นที่สแต็กที่ปลอดภัย
supercat

3
คำถามนี้มีประโยชน์ - ฉันต้องการล้มเหลวในการทดสอบหน่วยหากมีข้อยกเว้นของสแต็กล้นเกิดขึ้น - แต่ NUnit เพียงแค่ย้ายการทดสอบไปยังหมวดหมู่ "ละเว้น" แทนที่จะล้มเหลวเหมือนที่ทำกับข้อยกเว้นอื่น ๆ - ฉันจำเป็นต้องตรวจสอบ และทำAssert.Failแทน อย่างจริงจัง - เราจะทำอย่างไรกับเรื่องนี้?
BrainSlugs83

คำตอบ:


110

เริ่มต้นด้วย 2.0 ข้อยกเว้น StackOverflow สามารถตรวจจับได้ในสถานการณ์ต่อไปนี้เท่านั้น

  1. CLR กำลังทำงานในสภาพแวดล้อมที่โฮสต์*ซึ่งโฮสต์อนุญาตโดยเฉพาะสำหรับการจัดการข้อยกเว้น StackOverflow
  2. ข้อยกเว้น stackoverflow ถูกส่งโดยรหัสผู้ใช้และไม่ได้เกิดจากสถานการณ์สแตกล้นจริง ( การอ้างอิง )

* "สภาพแวดล้อมที่โฮสต์" เช่นเดียวกับใน "รหัสของฉันโฮสต์ CLR และฉันกำหนดค่าตัวเลือกของ CLR" ไม่ใช่ "รหัสของฉันทำงานบนโฮสติ้งที่ใช้ร่วมกัน"


29
หากไม่สามารถจับได้ใน scebario ที่เกี่ยวข้องเหตุใดจึงมีวัตถุ StackoverflowException
Manu

9
@Manu ด้วยเหตุผลอย่างน้อยสองประการ 1) มันถูกจับได้หรือเปล่าประเภท 1.1 และด้วยเหตุนี้จึงมีจุดประสงค์ 2) ยังสามารถจับได้หากคุณเป็นเจ้าภาพ CLR ดังนั้นจึงยังคงเป็นประเภทข้อยกเว้นที่ถูกต้อง
JaredPar

3
ถ้าจับไม่ได้ ... ทำไม windows event ไม่อธิบายสิ่งที่เกิดขึ้นรวม full stack trace ตามค่าเริ่มต้น?

11
จะดำเนินการอย่างไรในการอนุญาตให้จัดการ StackOverflowExceptions ในสภาพแวดล้อมที่โฮสต์ เหตุผลที่ฉันถามคือเพราะฉันใช้สภาพแวดล้อมที่โฮสต์และฉันมีปัญหานี้แน่นอนซึ่งมันทำลายกลุ่มแอปทั้งหมด ฉันอยากให้มันยกเลิกเธรดมากซึ่งมันสามารถคลายการสำรองข้อมูลไปด้านบนจากนั้นฉันสามารถบันทึกข้อผิดพลาดและดำเนินการต่อโดยไม่ต้องฆ่าเธรดของแอปพูลทั้งหมด
Brain2000

Starting with 2.0 ...ฉันอยากรู้อยากเห็นอะไรที่ทำให้พวกเขาไม่สามารถจับ SO ได้และเป็นไปได้อย่างไร1.1(คุณพูดถึงสิ่งนั้นในความคิดเห็นของคุณ)
M.kazem Akhgary

47

วิธีที่ถูกต้องคือการแก้ไขปัญหาน้ำล้น แต่ ....

คุณสามารถให้กองใหญ่ตัวเอง: -

using System.Threading;
Thread T = new Thread(threadDelegate, stackSizeInBytes);
T.Start();

คุณสามารถใช้คุณสมบัติ System.Diagnostics.StackTrace FrameCount เพื่อนับเฟรมที่คุณใช้และโยนข้อยกเว้นของคุณเองเมื่อถึงขีด จำกัด เฟรม

หรือคุณสามารถคำนวณขนาดของสแต็กที่เหลืออยู่และโยนข้อยกเว้นของคุณเองเมื่อมันต่ำกว่าเกณฑ์: -

class Program
{
    static int n;
    static int topOfStack;
    const int stackSize = 1000000; // Default?

    // The func is 76 bytes, but we need space to unwind the exception.
    const int spaceRequired = 18*1024; 

    unsafe static void Main(string[] args)
    {
        int var;
        topOfStack = (int)&var;

        n=0;
        recurse();
    }

    unsafe static void recurse()
    {
        int remaining;
        remaining = stackSize - (topOfStack - (int)&remaining);
        if (remaining < spaceRequired)
            throw new Exception("Cheese");
        n++;
        recurse();
    }
}

เพียงแค่จับชีส ;)


48
Cheeseยังห่างไกลจากความเฉพาะเจาะจง ฉันจะไปthrow new CheeseException("Gouda");
C.Evenhuis

14
@ C.Evenhuis ในขณะที่ไม่ต้องสงสัยเลยว่า Gouda เป็นชีสพิเศษที่ควรจะเป็น RollingCheeseException ("Double Gloucester") จริงๆเห็นcheese-rolling.co.uk

3
ฮ่า ๆ 1) ไม่สามารถแก้ไขได้เพราะหากไม่จับมันคุณมักไม่รู้ว่ามันเกิดขึ้นที่ไหน 2) การเพิ่ม Stacksize นั้นไร้ประโยชน์ด้วยการเรียกซ้ำที่ไม่มีที่สิ้นสุดและ m 3) การตรวจสอบ Stack ในตำแหน่งที่ถูกต้องก็เหมือนกับครั้งแรก
Firo

2
แต่ฉันแพ้แลคโตส
redoc

39

จากหน้า MSDN บนStackOverflowException s:

ในเวอร์ชันก่อนหน้าของ. NET Framework แอปพลิเคชันของคุณสามารถตรวจจับวัตถุ StackOverflowException (ตัวอย่างเช่นเพื่อกู้คืนจากการเรียกซ้ำที่ไม่ถูกผูกมัด) อย่างไรก็ตามแนวทางปฏิบัติดังกล่าวไม่ได้รับการสนับสนุนในขณะนี้เนื่องจากจำเป็นต้องใช้รหัสเพิ่มเติมจำนวนมากเพื่อตรวจจับข้อยกเว้นสแต็กโอเวอร์โฟลว์ได้อย่างน่าเชื่อถือและดำเนินการโปรแกรมต่อไป

เริ่มต้นด้วย. NET Framework เวอร์ชัน 2.0 อ็อบเจ็กต์ StackOverflowException ไม่สามารถจับได้โดยบล็อก try-catch และกระบวนการที่เกี่ยวข้องจะถูกยกเลิกโดยค่าเริ่มต้น ดังนั้นผู้ใช้ควรเขียนโค้ดเพื่อตรวจจับและป้องกันไม่ให้สแตกล้น ตัวอย่างเช่นหากแอปพลิเคชันของคุณขึ้นอยู่กับการเรียกซ้ำให้ใช้ตัวนับหรือเงื่อนไขสถานะเพื่อยุติการวนซ้ำ โปรดสังเกตว่าแอ็พพลิเคชันที่โฮสต์รันไทม์ภาษาทั่วไป (CLR) สามารถระบุว่า CLR ยกเลิกการโหลดโดเมนแอ็พพลิเคชันที่เกิดข้อยกเว้นของสแตกล้นและปล่อยให้กระบวนการที่เกี่ยวข้องดำเนินต่อไป สำหรับข้อมูลเพิ่มเติมโปรดดู ICLRPolicyManager Interface และ Hosting the Common Language Runtime


23

ดังที่ผู้ใช้หลายคนได้กล่าวไปแล้วคุณไม่สามารถจับข้อยกเว้นได้ อย่างไรก็ตามหากคุณกำลังดิ้นรนเพื่อหาว่ามันเกิดขึ้นที่ไหนคุณอาจต้องการกำหนดค่าสตูดิโอภาพให้พังเมื่อมันถูกโยนทิ้ง

ในการทำเช่นนั้นคุณต้องเปิด Exception Settings จากเมนู 'Debug' ใน Visual Studio เวอร์ชันเก่าจะอยู่ที่ 'Debug' - 'exceptions'; ในเวอร์ชันที่ใหม่กว่าจะอยู่ที่ 'Debug' - 'Windows' - 'Exception Settings'

เมื่อคุณเปิดการตั้งค่าแล้วให้ขยาย 'ข้อยกเว้นรันไทม์ภาษาทั่วไป' ขยาย 'ระบบ' เลื่อนลงและเลือก 'System.StackOverflowException' จากนั้นคุณสามารถดูที่กองการโทรและมองหารูปแบบการโทรซ้ำ ซึ่งจะช่วยให้คุณทราบถึงตำแหน่งที่จะแก้ไขโค้ดที่ทำให้สแต็กล้น


1
Debug - ข้อยกเว้นใน VS 2015 อยู่ที่ไหน?
FrenkyB

1
Debug - Windows - Exception Settings
Simon

15

ดังที่ได้กล่าวไว้ข้างต้นหลายครั้งไม่สามารถตรวจจับ StackOverflowException ที่เพิ่มขึ้นโดยระบบเนื่องจากสถานะของกระบวนการเสียหาย แต่มีวิธีสังเกตข้อยกเว้นเป็นเหตุการณ์:

http://msdn.microsoft.com/en-us/library/system.appdomain.unhandledexception.aspx

เริ่มต้นด้วย. NET Framework เวอร์ชัน 4 เหตุการณ์นี้จะไม่ถูกยกขึ้นสำหรับข้อยกเว้นที่ทำให้สถานะของกระบวนการเสียหายเช่นสแต็กล้นหรือการละเมิดการเข้าถึงเว้นแต่ตัวจัดการเหตุการณ์จะมีความสำคัญด้านความปลอดภัยและมีแอตทริบิวต์ HandleProcessCorruptedStateExceptionsAttribute

อย่างไรก็ตามแอปพลิเคชันของคุณจะสิ้นสุดลงหลังจากออกจากฟังก์ชั่นเหตุการณ์ (วิธีแก้ปัญหาที่สกปรกมากคือการรีสตาร์ทแอปภายในเหตุการณ์นี้ฮ่าฮ่าไม่ได้ทำเช่นนั้นและจะไม่ทำ) แต่ก็ดีพอสำหรับการลงไม้!

ใน. NET Framework เวอร์ชัน 1.0 และ 1.1 ข้อยกเว้นที่ไม่สามารถจัดการได้ที่เกิดขึ้นในเธรดอื่นที่ไม่ใช่เธรดแอ็พพลิเคชันหลักถูกจับโดยรันไทม์ดังนั้นจึงไม่ทำให้แอ็พพลิเคชันยุติการทำงาน ดังนั้นจึงเป็นไปได้ที่เหตุการณ์ UnhandledException จะถูกยกขึ้นโดยที่แอปพลิเคชันไม่ยุติ เริ่มต้นด้วย. NET Framework เวอร์ชัน 2.0 แบ็คสต็อปสำหรับข้อยกเว้นที่ไม่ได้จัดการในเธรดชายด์ถูกลบออกเนื่องจากเอฟเฟกต์สะสมของความล้มเหลวแบบไม่โต้ตอบดังกล่าวรวมถึงการลดประสิทธิภาพข้อมูลที่เสียหายและการล็อกซึ่งทั้งหมดนี้ยากที่จะดีบัก สำหรับข้อมูลเพิ่มเติมรวมถึงรายการกรณีที่รันไทม์ไม่ยุติโปรดดูข้อยกเว้นในเธรดที่มีการจัดการ


6

ใช่จากการล้นสแต็ก CLR 2.0 ถือเป็นสถานการณ์ที่ไม่สามารถกู้คืนได้ ดังนั้นรันไทม์จึงยังคงปิดกระบวนการ

สำหรับรายละเอียดโปรดดูเอกสารhttp://msdn.microsoft.com/en-us/library/system.stackoverflowexception.aspx


จาก CLR 2.0 StackOverflowExceptionจะยุติกระบวนการโดยค่าเริ่มต้น
Brian Rasmussen

ไม่ได้คุณสามารถจับ OOM ได้และในบางกรณีก็อาจสมเหตุสมผลที่จะทำเช่นนั้น ฉันไม่รู้ว่าคุณหมายถึงอะไรที่ด้ายหายไป หากเธรดมีข้อยกเว้นที่ไม่สามารถจัดการได้ CLR จะยุติกระบวนการ หากเธรดของคุณเสร็จสิ้นตามวิธีการของมันจะถูกล้าง
Brian Rasmussen

5

คุณทำไม่ได้ CLR ไม่ยอมให้คุณ สแต็กล้นเป็นข้อผิดพลาดร้ายแรงและไม่สามารถกู้คืนได้


ดังนั้นคุณจะทำให้การทดสอบหน่วยล้มเหลวสำหรับข้อยกเว้นนี้ได้อย่างไรถ้าแทนที่จะจับได้มันทำให้นักวิ่งทดสอบหน่วยล้มเหลวแทน?
BrainSlugs83

1
@ BrainSlugs83 คุณไม่ทำเพราะนั่นเป็นความคิดที่ไร้สาระ เหตุใดคุณจึงทดสอบว่าโค้ดของคุณล้มเหลวด้วย StackOverflowException หรือไม่ จะเกิดอะไรขึ้นหาก CLR เปลี่ยนแปลงเพื่อให้สามารถจัดการกับสแต็กที่ลึกกว่าได้ จะเกิดอะไรขึ้นถ้าคุณเรียกใช้ฟังก์ชันทดสอบหน่วยของคุณในที่ที่มีสแต็กซ้อนกันลึก ๆ อยู่แล้ว? ดูเหมือนว่าจะมีบางอย่างที่ไม่สามารถทดสอบได้ หากคุณพยายามโยนด้วยตนเองให้เลือกข้อยกเว้นที่ดีกว่าสำหรับงานนั้น
Matthew Scharley

5

คุณไม่สามารถอธิบายได้เนื่องจากโพสต์ส่วนใหญ่กำลังอธิบายให้ฉันเพิ่มพื้นที่อื่น:

ในหลาย ๆ เว็บไซต์คุณจะพบผู้คนบอกว่าวิธีหลีกเลี่ยงปัญหานี้คือการใช้ AppDomain อื่นดังนั้นหากสิ่งนี้เกิดขึ้นโดเมนจะถูกยกเลิกการโหลด นั่นเป็นความผิดอย่างยิ่ง (เว้นแต่คุณจะโฮสต์ CLR ของคุณ) เนื่องจากลักษณะการทำงานเริ่มต้นของ CLR จะเพิ่มเหตุการณ์ KillProcess ซึ่งจะนำ AppDomain เริ่มต้นของคุณมาลง


3

เป็นไปไม่ได้และด้วยเหตุผลที่ดี (สำหรับข้อหนึ่งให้คิดถึงสิ่งที่จับได้ทั้งหมด (ข้อยกเว้น) {} รอบ ๆ )

หากคุณต้องการดำเนินการต่อหลังจากที่สแต็กล้นให้เรียกใช้รหัสอันตรายใน AppDomain อื่น สามารถตั้งค่านโยบาย CLR เพื่อยุติ AppDomain ปัจจุบันเมื่อโอเวอร์โฟลว์โดยไม่ส่งผลกระทบต่อโดเมนดั้งเดิม


2
คำสั่ง "catch" จะไม่เป็นปัญหาจริงๆเนื่องจากเมื่อถึงเวลาที่คำสั่ง catch สามารถเรียกใช้งานระบบจะย้อนผลกระทบจากสิ่งที่พยายามใช้พื้นที่สองกองซ้อนกันมาก ไม่มีเหตุผลที่จะจับได้ว่าข้อยกเว้นของสแตกล้นจะต้องเป็นอันตราย เหตุผลที่พวกเขาไม่สามารถจับข้อยกเว้นดังกล่าวได้คือการอนุญาตให้จับได้อย่างปลอดภัยจะต้องมีการเพิ่มค่าโสหุ้ยพิเศษให้กับโค้ดทั้งหมดที่ใช้สแต็กแม้ว่าจะไม่ล้นก็ตาม
supercat

4
ในบางประเด็นคำแถลงไม่ได้รับการพิจารณาอย่างดี หากคุณไม่สามารถจับ Stackoverflow ได้คุณอาจไม่มีทางรู้ว่ามันเกิดขึ้นที่ไหนในสภาพแวดล้อมการใช้งานจริง
Offler
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.