โค้ดในคำสั่งในที่สุดจะเริ่มทำงานหรือไม่หากฉันส่งคืนค่าในบล็อกลองใช้


237

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

ตัวอย่าง:

public bool someMethod()
{
  try
  {
    return true;
    throw new Exception("test"); // doesn't seem to get executed
  }
  finally
  {
    //code in question
  }
}


การจัดการที่นี่หมายถึง: จับ แม้แต่บล็อก catch ว่างในตัวจัดการส่วนกลางของคุณก็เพียงพอแล้ว นอกจากนี้ยังมีข้อยกเว้นที่ไม่สามารถจัดการ: StackOverflowException, ExecutionEngineExceptionคือบางส่วนของผู้ที่ และเนื่องจากพวกเขาไม่สามารถจัดการได้finallyจะไม่ทำงาน
Abel

8
@Abel: ดูเหมือนว่าคุณกำลังพูดถึงสถานการณ์ที่แตกต่าง คำถามนี้เกี่ยวกับการกลับมาเป็นtryบล็อก ไม่มีอะไรที่เกี่ยวกับโปรแกรมที่ฉับพลันยกเลิก
Jon Skeet

6
@Abel: ฉันไม่แน่ใจว่าคุณหมายถึงอะไรโดย "กลับมาหลังจากมีข้อยกเว้น" แต่ดูเหมือนจะไม่เป็นสิ่งที่ถูกถามเกี่ยวกับที่นี่ ดูรหัส - คำสั่งแรกของtryบล็อกคือreturnคำสั่ง (คำแถลงข้อที่สองของบล็อกนั้นไม่สามารถเข้าถึงได้และจะสร้างคำเตือน)
Jon Skeet

4
@Abel: แน่นอนและหากคำถามคือ "จะรหัสในคำสั่งในที่สุดก็จะดำเนินการในทุกสถานการณ์" ที่จะเกี่ยวข้อง แต่นั่นไม่ใช่สิ่งที่ถูกถาม
Jon Skeet

คำตอบ:


265

คำตอบง่ายๆ: ใช่


9
@Edi: อืมฉันไม่เห็นว่าเธรดพื้นหลังเกี่ยวข้องกับอะไร โดยพื้นฐานแล้วหากกระบวนการทั้งหมดยกเลิกการfinallyบล็อกจะดำเนินการ
Jon Skeet

3
คำตอบยาว ๆ คือคุณไม่จำเป็นต้องให้บล็อกถูกเรียกใช้ในที่สุดหากมีบางสิ่งที่ร้ายแรงเกิดขึ้นเช่น Stack Overflow, Out of Memory exception, ฮาร์ดไดรฟ์ผิดปกติหรือหากมีใครถอดปลั๊กเครื่องของคุณในเวลาที่เหมาะสม . แต่สำหรับทุกเจตนาและทุกจุดประสงค์เว้นแต่ว่าคุณกำลังทำสิ่งที่ผิดอย่างมากบล็อกสุดท้ายก็จะยิง
Andrew Rollings

หากรหัสก่อนลองล้มเหลวเนื่องจากเหตุผลบางอย่างฉันสังเกตเห็นว่าในที่สุดก็ยังได้รับการดำเนินการ ในกรณีนี้คุณสามารถเพิ่มเงื่อนไขสำหรับในที่สุดซึ่งเป็นไปตามเกณฑ์ที่จะดำเนินการในที่สุดก็ต่อเมื่อรหัสภายใต้การดำเนินการประสบความสำเร็จ
kjosh

200

ตามปกติใช่ ส่วนสุดท้ายรับประกันว่าจะดำเนินการสิ่งที่เกิดขึ้นรวมถึงข้อยกเว้นหรือคำสั่งกลับ ข้อยกเว้นของกฎนี้เป็นข้อยกเว้นแบบอะซิงโครนัสที่เกิดขึ้นบนเธรด ( OutOfMemoryException, StackOverflowException)

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


154

นี่คือการทดสอบเล็กน้อย:

class Class1
{
    [STAThread]
    static void Main(string[] args)
    {
        Console.WriteLine("before");
        Console.WriteLine(test());
        Console.WriteLine("after");
    }

    static string test()
    {
        try
        {
            return "return";
        }
        finally
        {
            Console.WriteLine("finally");
        }
    }
}

ผลลัพธ์คือ:

before
finally
return
after

10
@CodeBlend: มันจะทำอย่างไรกับเมื่อWriteLineสำหรับผลของการเรียกวิธีการที่จะดำเนินการจริง ในกรณีนี้การreturnโทรจะตั้งค่าผลลัพธ์เป็นวิธีสุดท้ายเขียนไปยังคอนโซล - ก่อนที่เมธอดจะออก จากนั้นWriteLineในวิธีการหลักจะแยกข้อความออกจากการโทรกลับ
NotMe

38

การอ้างอิงจาก MSDN

ในที่สุดก็ถูกนำมาใช้เพื่อรับประกันบล็อกงบรันรหัสโดยไม่คำนึงถึงวิธีการที่ก่อนหน้านี้พยายามบล็อกออก


3
นอกจากกรณีพิเศษของ Mehrdad แล้วในที่สุดก็จะไม่ถูกเรียกถ้าคุณทำการดีบั๊กในบล็อกลองแล้วหยุดดีบั๊ก :) ไม่มีสิ่งใดในชีวิตที่รับประกันได้ดูเหมือนว่า
StuartLC

1
ไม่ค่อนข้างมีการอ้างอิงจาก MSDN : อย่างไรก็ตามหากข้อยกเว้นไม่ถูกจัดการการดำเนินการของบล็อกสุดท้ายจะขึ้นอยู่กับวิธีการดำเนินการยกเว้นข้อยกเว้นถูกเรียกใช้ ในที่สุดก็ขึ้นอยู่กับการตั้งค่าคอมพิวเตอร์ของคุณ .
Abel

1
@Abel ทำให้เป็นจุดสำคัญที่นี่ ทุกคนสามารถเห็นได้ว่าการบล็อกในท้ายที่สุดจะไม่ถูกดำเนินการอย่างไรหากโปรแกรมถูกยกเลิกผ่านตัวจัดการงาน แต่คำตอบนี้ก็คือผิดธรรมดา: finallyในความเป็นจริงไม่มีอะไรรับประกันแม้แต่โปรแกรมทั่วไปที่สมบูรณ์แบบ (ซึ่งเกิดขึ้นเพื่อโยนข้อยกเว้นนี้)
ปีเตอร์ - Reinstate Monica

19

โดยทั่วไปแล้วในที่สุดก็จะทำงาน

สำหรับสามสถานการณ์ต่อไปนี้ในที่สุดจะรันเสมอ :

  1. ไม่มีข้อยกเว้นเกิดขึ้น
  2. ข้อยกเว้นแบบซิงโครนัส (ข้อยกเว้นที่เกิดขึ้นในโฟลว์โปรแกรมปกติ)
    ซึ่งรวมถึงข้อยกเว้นที่สอดคล้องกับ CLS ที่ได้รับมาจากข้อยกเว้นของ System.Exception และ non-CLS ซึ่งไม่ได้มาจาก System.Exception ข้อยกเว้นที่สอดคล้องกับ Non-CLS จะถูกห่อโดยอัตโนมัติโดย RuntimeWrappedException C # ไม่สามารถโยนข้อยกเว้นการร้องเรียนที่ไม่ใช่ CLS แต่ภาษาเช่น C ++ สามารถ C # อาจเรียกใช้รหัสที่เขียนด้วยภาษาที่สามารถยกเว้นข้อยกเว้นที่ไม่สอดคล้องกับ CLS
  3. Asynchronous ThreadAbortException
    ณ . NET 2.0, ThreadAbortException จะไม่ป้องกันการเรียกใช้ในที่สุด ThreadAbortException จะถูกยกไปก่อนหรือหลังในที่สุด ในที่สุดจะทำงานเสมอและจะไม่ถูกขัดจังหวะโดยการยกเลิกเธรดตราบใดที่การลองถูกป้อนจริงก่อนที่การยกเลิกเธรดจะเกิดขึ้น

สถานการณ์สมมติต่อไปนี้ในที่สุดจะไม่ทำงาน:

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

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

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

เช่น,

// No code can appear after this line, before the try
RuntimeHelpers.PrepareConstrainedRegions();
try
{ 
    // This is *NOT* a CER
}
finally
{
    // This is a CER; guaranteed to run, if the try was entered, 
    // even if a StackOverflowException occurs.
}

โปรดทราบว่าแม้ CER จะไม่ทำงานในกรณีของ SOE ในขณะที่คุณเขียนเอกสาร MSDN ใน CER ผิด / ไม่สมบูรณ์ SOE จะทริกเกอร์FailFastภายใน วิธีเดียวที่ฉันจัดการที่จะจับเหล่านั้นโดยการปรับแต่งรันไทม์ CLR โฮสติ้ง โปรดทราบว่าจุดของคุณยังคงใช้ได้สำหรับข้อยกเว้นแบบอะซิงโครนัสอื่น ๆ
อาเบล

10

มีข้อยกเว้นที่สำคัญมากสำหรับสิ่งนี้ซึ่งฉันไม่ได้เห็นพูดถึงในคำตอบอื่นใดและ (หลังจากการเขียนโปรแกรมใน C # เป็นเวลา 18 ปี) ฉันไม่อยากจะเชื่อเลยว่าฉันไม่รู้

หากคุณโยนหรือทริกเกอร์ข้อยกเว้นของการเรียงลำดับใด ๆในcatchบล็อกของคุณ(ไม่ใช่แค่แปลก ๆStackOverflowExceptionsและสิ่งของอื่น ๆของบล็อกนั้น) และคุณไม่มีtry/catch/finallyบล็อกทั้งหมดในบล็อกอื่นtry/catchบล็อกของคุณfinallyจะไม่ทำงาน นี่แสดงให้เห็นได้อย่างง่ายดาย - และถ้าฉันไม่ได้เห็นมันด้วยตัวเองบ่อยแค่ไหนที่ฉันได้อ่านว่ามันเป็นแค่มุมเล็ก ๆ แปลก ๆ ที่สามารถทำให้finallyบล็อกไม่ทำงานฉันจะไม่เชื่อ

static void Main(string[] args)
{
    Console.WriteLine("Beginning demo of how finally clause doesn't get executed");
    try
    {
        Console.WriteLine("Inside try but before exception.");
        throw new Exception("Exception #1");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Inside catch for the exception '{ex.Message}' (before throwing another exception).");
        throw;
    }
    finally
    {
        Console.WriteLine("This never gets executed, and that seems very, very wrong.");
    }

    Console.WriteLine("This never gets executed, but I wasn't expecting it to."); 
    Console.ReadLine();
}

ฉันแน่ใจว่ามีเหตุผลสำหรับสิ่งนี้ แต่มันแปลกประหลาดที่ไม่เป็นที่รู้จักมากขึ้น (มันถูกบันทึกไว้ที่นี่แต่ไม่ใช่ที่ใดในคำถามนี้)


ดีfinallyบล็อกก็ไม่จับสำหรับcatchบล็อก
Peter - Reinstate Monica

7

ฉันรู้ว่าฉันไปงานปาร์ตี้ช้า แต่ในสถานการณ์ (แตกต่างจากตัวอย่างของ OP) ที่จริง ๆ แล้วมีข้อยกเว้นเกิดขึ้นในสถานะ MSDN ( https://msdn.microsoft.com/en-us/library/zwc8s4fz.aspx ): "หากไม่พบข้อยกเว้นการประมวลผลของบล็อกในที่สุดจะขึ้นอยู่กับว่าระบบปฏิบัติการเลือกที่จะเปิดใช้งานข้อยกเว้นเพื่อคลายการทำงาน"

ในที่สุดบล็อกก็รับประกันว่าจะดำเนินการถ้าฟังก์ชั่นอื่น ๆ (เช่นหลัก) ขึ้นไปสแต็คการเรียกจับข้อยกเว้น รายละเอียดนี้มักจะไม่เกิดปัญหาเนื่องจากโปรแกรม C # ทั้งหมดที่ทำงานในสภาพแวดล้อมรันไทม์ (CLR และ OS) ทำงานบนทรัพยากรส่วนใหญ่ฟรีที่กระบวนการเป็นเจ้าของเมื่อออกจาก (จัดการกับไฟล์ ฯลฯ ) ในบางกรณีอาจมีความสำคัญ: การดำเนินการฐานข้อมูลครึ่งทางที่คุณต้องการที่จะยอมรับการตอบสนอง คลี่คลาย; หรือการเชื่อมต่อระยะไกลบางอย่างซึ่งอาจไม่ถูกปิดโดยอัตโนมัติโดยระบบปฏิบัติการแล้วบล็อกเซิร์ฟเวอร์


3

ใช่. นั่นคือความจริงที่ว่าประเด็นหลักของคำสั่งในที่สุด ยกเว้นบางสิ่งบางอย่าง catestrophic เกิดขึ้น (หน่วยความจำไม่ได้ถอดปลั๊กคอมพิวเตอร์ ฯลฯ ) คำสั่งในที่สุดควรจะถูกดำเนินการเสมอ


1
ฉันขอไม่เห็นด้วย cf เลย คำตอบของฉัน. ข้อยกเว้นปกติอย่างสมบูรณ์ในบล็อกการลองนั้นเพียงพอที่จะข้ามfinallyบล็อกได้หากไม่ได้รับการยกเว้น นั่นทำให้ฉันประหลาดใจ ;-)
Peter - Reinstate Monica

@ PeterA.Schneider - น่าสนใจ แน่นอนว่าความหมายของมันไม่ได้เปลี่ยนไปมากนัก ถ้าไม่มีอะไรมากไปที่ call stack จะตรวจจับข้อยกเว้นนั่นก็ควรหมายความว่า (ถ้าฉันเข้าใจถูกต้อง) ว่ากระบวนการกำลังจะถูกยกเลิก ดังนั้นจึงคล้ายกับสถานการณ์การดึงปลั๊ก ฉันเดาว่าสิ่งที่ต้องทำในตอนนี้คือคุณควรจะมีการจับระดับสูงสุดหรือข้อยกเว้นที่ไม่สามารถจัดการได้เสมอ
Jeffrey L Whitledge

นั่นคือสิ่งที่ฉันเข้าใจเช่นกัน ฉันพบว่าน่าแปลกใจเช่นกัน จริง: ทรัพยากรที่พบบ่อยที่สุดจะถูกปล่อยออกมาโดยอัตโนมัติในหน่วยความจำเฉพาะและไฟล์ที่เปิด แต่ทรัพยากรที่ระบบปฏิบัติการไม่รู้จัก - เปิดเซิร์ฟเวอร์หรือการเชื่อมต่อฐานข้อมูลเป็นตัวอย่าง - อาจยังคงเปิดอยู่ในด้านระยะไกลเพราะพวกเขาไม่เคยถูกปิดอย่างถูกต้อง ซ็อกเก็ตยังคงเอ้อระเหยและอื่น ๆ ฉันคิดว่ามันรอบคอบที่จะมีตัวจัดการข้อยกเว้นระดับบนสุดใช่
Peter - Reinstate Monica


2

ในที่สุดก็จะไม่ทำงานในกรณีที่คุณออกจากแอปพลิเคชันโดยใช้ System.exit (0); เช่นเดียวกับใน

try
{
    System.out.println("try");
    System.exit(0);
}
finally
{
   System.out.println("finally");
}

ผลลัพธ์จะเป็นเพียง: ลอง


4
คุณตอบในภาษาที่ไม่ถูกต้องผมคิดว่าคำถามที่เกี่ยวกับแต่นี้น่าจะเป็นc# Javaและนอกจากนั้นในกรณีส่วนใหญ่System.exit()คำใบ้ของการออกแบบที่ไม่ดีคือ :-)
z00l

0

99% ของสถานการณ์สมมติว่าจะรับประกันได้ว่าโค้ดภายในfinallyบล็อกจะทำงานอย่างไรก็ตามคิดว่ามีสถานการณ์นี้: คุณมีเธรดที่มีtry-> finallyบล็อก (ไม่catch) และคุณได้รับข้อยกเว้นที่ไม่สามารถจัดการได้ภายในเธรดนั้น ในกรณีนี้เธรดจะออกและfinallyบล็อกจะไม่ถูกดำเนินการ (แอปพลิเคชันสามารถทำงานต่อในกรณีนี้)

สถานการณ์นี้ค่อนข้างหายาก แต่มีเพียงเพื่อแสดงให้เห็นว่าคำตอบไม่ใช่ "ใช่" เสมอเป็นส่วนใหญ่ "ใช่" และบางครั้งในเงื่อนไขที่หายาก "ไม่"


0

วัตถุประสงค์หลักของการปิดกั้นในที่สุดคือการดำเนินการสิ่งที่เขียนไว้ข้างใน มันไม่ควรขึ้นอยู่กับสิ่งที่เกิดขึ้นในการลองหรือการจับอย่างไรก็ตามด้วย System.Environment.Exit (1) แอปพลิเคชันจะออกโดยไม่ย้ายไปที่บรรทัดถัดไปของรหัส

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