จะเกิดอะไรขึ้นหากในที่สุดบล็อกมีข้อผิดพลาดเกิดขึ้น


266

หากบล็อกสุดท้ายพ่นยกเว้นสิ่งที่ว่าที่เกิดขึ้น?

โดยเฉพาะจะเกิดอะไรขึ้นถ้าข้อยกเว้นถูกโยนลงครึ่งหนึ่งผ่านบล็อกสุดท้าย ส่วนที่เหลือของคำสั่ง (หลังจาก) ในบล็อกนี้ถูกเรียก?

ฉันรู้ว่าข้อยกเว้นจะแพร่กระจายขึ้นไป


8
ทำไมไม่ลองมันดูล่ะ? แต่ในสิ่งต่าง ๆ นี้สิ่งที่ฉันชอบมากที่สุดคือการกลับมาก่อนในที่สุดและจากนั้นก็คืนสิ่งอื่นจากบล็อกในที่สุด :)
ตอบที่

8
คำสั่งทั้งหมดในบล็อกในที่สุดจะต้องดำเนินการ ไม่สามารถคืนสินค้าได้ msdn.microsoft.com/en-us/library/0hbbzekw(VS.80).aspx
Tim Scarborough

คำตอบ:


419

หากบล็อกในที่สุดก็โยนข้อยกเว้นสิ่งที่ว่าที่เกิดขึ้น?

ข้อยกเว้นนั้นแพร่กระจายออกไปและขึ้นและจะ (สามารถ) ได้รับการจัดการในระดับที่สูงขึ้น

ในที่สุดบล็อกของคุณจะไม่เสร็จสมบูรณ์เกินกว่าจุดที่มีการโยนข้อยกเว้น

หากบล็อกสุดท้ายถูกเรียกใช้งานระหว่างการจัดการข้อยกเว้นก่อนหน้านี้แสดงว่าข้อยกเว้นแรกหายไป

ข้อมูลจำเพาะภาษา C # 4 § 8.9.5: หากบล็อกสุดท้ายส่งข้อยกเว้นอื่นการประมวลผลข้อยกเว้นปัจจุบันจะถูกยกเลิก


9
เว้นแต่ว่าจะเป็น a ThreadAbortExceptionบล็อกทั้งหมดในที่สุดจะเสร็จสิ้นก่อนเนื่องจากเป็นส่วนที่สำคัญ
Dmytro Shevchenko

1
@Shedal - คุณพูดถูก แต่ใช้กับ "ข้อยกเว้นแบบอะซิงโครนัสบางอย่าง" เท่านั้นเช่น ThreadAbortException สำหรับรหัส 1 เธรดปกติคำตอบของฉันจะคงอยู่
Henk Holterman

"ข้อยกเว้นแรกหายไป" - จริง ๆ แล้วน่าผิดหวังมากโดยบังเอิญฉันพบวัตถุ IDisposable ที่ส่งข้อยกเว้นใน Dispose () ซึ่งส่งผลให้เกิดข้อยกเว้นภายใน "ใช้" ข้อ
Alex Burtsev

"ฉันพบวัตถุ IDisposable ที่มีข้อยกเว้นใน Dispose ()" - มันแปลกที่จะพูดน้อย อ่านใน MSDN: หลีกเลี่ยงการโยนข้อยกเว้นจากภายในการกำจัด (บูล) ยกเว้นภายใต้ ...
Henk Holterman

1
@HenkHolterman: ข้อผิดพลาดของดิสก์ไม่ได้เกิดขึ้นบ่อยนักบนฮาร์ดดิสก์หลักที่เชื่อมต่อโดยตรง แต่บางครั้งโปรแกรมก็เขียนไฟล์ลงดิสก์ที่ถอดออกได้หรือเครือข่าย ปัญหาสามารถพบได้บ่อยกับสิ่งเหล่านั้น หากมีใครบางคนเอาแท่ง USB ออกก่อนที่ไฟล์จะถูกเขียนมันจะเป็นการดีกว่าที่จะบอกพวกเขาทันทีกว่ารอจนกว่าพวกเขาจะไปถึงที่ที่พวกเขากำลังจะไปและพบว่าไฟล์นั้นเสียหาย การทำตามข้อผิดพลาดก่อนหน้านี้เมื่อมีสิ่งใดสิ่งหนึ่งอาจเป็นพฤติกรรมที่เหมาะสม แต่เมื่อไม่มีข้อผิดพลาดก่อนหน้านี้มันจะเป็นการดีกว่าที่จะรายงานปัญหามากกว่าปล่อยให้มันถูกรายงาน
supercat

101

สำหรับคำถามเช่นนี้ฉันมักจะเปิดโครงการแอปพลิเคชั่นคอนโซลว่างใน Visual Studio และเขียนโปรแกรมตัวอย่างขนาดเล็ก:

using System;

class Program
{
    static void Main(string[] args)
    {
        try
        {
            try
            {
                throw new Exception("exception thrown from try block");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Inner catch block handling {0}.", ex.Message);
                throw;
            }
            finally
            {
                Console.WriteLine("Inner finally block");
                throw new Exception("exception thrown from finally block");
                Console.WriteLine("This line is never reached");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Outer catch block handling {0}.", ex.Message);
        }
        finally
        {
            Console.WriteLine("Outer finally block");
        }
    }
}

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

ข้อยกเว้นการจัดการบล็อก catch ภายในถูกส่งออกจาก try block
ในที่สุดก็ปิดกั้น
ข้อยกเว้นการจัดการบล็อก catch ภายนอกถูกโยนออกจากบล็อกในที่สุด
บล็อกนอกสุด

หมายเหตุเพิ่มเติม

ดังที่ไมเคิลดามาตอฟชี้ให้เห็นข้อยกเว้นจากการtryบล็อกจะ "กิน" ถ้าคุณไม่จัดการมันในcatchบล็อก(ด้านใน) ในความเป็นจริงในตัวอย่างข้างต้นข้อยกเว้นการโยนใหม่ไม่ปรากฏในบล็อก catch ภายนอก เพื่อทำให้ชัดเจนยิ่งขึ้นในตัวอย่างที่แก้ไขเล็กน้อยต่อไปนี้:

using System;

class Program
{
    static void Main(string[] args)
    {
        try
        {
            try
            {
                throw new Exception("exception thrown from try block");
            }
            finally
            {
                Console.WriteLine("Inner finally block");
                throw new Exception("exception thrown from finally block");
                Console.WriteLine("This line is never reached");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Outer catch block handling {0}.", ex.Message);
        }
        finally
        {
            Console.WriteLine("Outer finally block");
        }
    }
}

ดังที่คุณเห็นจากผลลัพธ์ข้อยกเว้นภายในคือ "หลงทาง" (เช่นถูกละเว้น):

ในที่สุดก็ปิดกั้น
ข้อยกเว้นการจัดการบล็อก catch ภายนอกถูกโยนออกจากบล็อกในที่สุด
บล็อกนอกสุด

2
เนื่องจากคุณโยนข้อยกเว้นในการจับภายในของคุณ 'ในที่สุดบล็อกใน' จะไม่สามารถเข้าถึงได้ในตัวอย่างนี้
Theofanis Pantelides

4
@Theofanis Pantelides: ไม่finallyบล็อกจะถูกดำเนินการเกือบทุกครั้งซึ่งจะเป็นกรณีนี้สำหรับบล็อกในที่สุด (เพียงลองโปรแกรมตัวอย่างด้วยตัวเอง (บล็อกสุดท้ายจะไม่ถูกดำเนินการในกรณีที่ไม่สามารถกู้คืนได้) ยกเว้นเช่นEngineExecutionException, แต่ในกรณีเช่นนี้โปรแกรมของคุณจะยุติทันที)
Dirk Vollmar

1
ฉันไม่เห็นว่าบทบาทของการโยนในการจับครั้งแรกของรหัสชิ้นแรกของคุณคืออะไร ฉันลองด้วยและไม่ใช้มันด้วยแอปพลิเคชั่นคอนโซลไม่พบความแตกต่าง
JohnPan

@ Johnpan: จุดประสงค์เพื่อแสดงให้เห็นว่าการบล็อกในที่สุดจะดำเนินการเสมอแม้ว่าทั้งคู่จะพยายามจับบล็อกก็ตาม ไม่มีความแตกต่างในเอาต์พุตคอนโซล
Dirk Vollmar

10

หากมีข้อยกเว้นที่ค้างอยู่ (เมื่อtryบล็อกมีfinallyแต่ไม่มีcatch) ข้อยกเว้นใหม่จะแทนที่ข้อผิดพลาดนั้น

หากไม่มีข้อยกเว้นรอดำเนินการมันก็เหมือนกับการโยนข้อยกเว้นนอกfinallyบล็อก


ข้อยกเว้นนอกจากนี้ยังอาจจะอยู่ระหว่างดำเนินการถ้ามีคือจับคู่catchบล็อกที่ (ใหม่) พ่นข้อยกเว้น
stakx - ไม่สนับสนุน


3

ส่วนย่อยอย่างรวดเร็ว (และค่อนข้างชัดเจน) เพื่อบันทึก "ข้อยกเว้นดั้งเดิม" (โยนในtryบล็อก) และเสียสละ "ในที่สุดยกเว้น" (โยนลงในfinallyบล็อก) ในกรณีที่ต้นฉบับมีความสำคัญสำหรับคุณ:

try
{
    throw new Exception("Original Exception");
}
finally
{
    try
    {
        throw new Exception("Finally Exception");
    }
    catch
    { }
}

เมื่อมีการใช้งานโค้ดข้างต้น "ข้อยกเว้นดั้งเดิม" จะส่งผ่านสแต็กการโทรขึ้นและ "การยกเว้นขั้นสุดท้าย" จะหายไป


2

ฉันต้องทำเช่นนี้เพื่อจับข้อผิดพลาดพยายามปิดกระแสที่ไม่เคยเปิดเพราะมีข้อยกเว้น

errorMessage = string.Empty;

try
{
    byte[] requestBytes = System.Text.Encoding.ASCII.GetBytes(xmlFileContent);

    webRequest = WebRequest.Create(url);
    webRequest.Method = "POST";
    webRequest.ContentType = "text/xml;charset=utf-8";
    webRequest.ContentLength = requestBytes.Length;

    //send the request
    using (var sw = webRequest.GetRequestStream()) 
    {
        sw.Write(requestBytes, 0, requestBytes.Length);
    }

    //get the response
    webResponse = webRequest.GetResponse();
    using (var sr = new StreamReader(webResponse.GetResponseStream()))
    {
        returnVal = sr.ReadToEnd();
        sr.Close();
    }
}
catch (Exception ex)
{
    errorMessage = ex.ToString();
}
finally
{
    try
    {
        if (webRequest.GetRequestStream() != null)
            webRequest.GetRequestStream().Close();
        if (webResponse.GetResponseStream() != null)
            webResponse.GetResponseStream().Close();
    }
    catch (Exception exw)
    {
        errorMessage = exw.ToString();
    }
}

ถ้า webRequest ถูกสร้างขึ้น แต่เกิดข้อผิดพลาดในการเชื่อมต่อระหว่าง

using (var sw = webRequest.GetRequestStream())

ในที่สุดก็จะมีข้อยกเว้นพยายามปิดการเชื่อมต่อที่คิดว่าเปิดเพราะ webRequest ถูกสร้างขึ้น

หากในที่สุดไม่ได้ลองภายในโค้ดนี้จะทำให้เกิดข้อยกเว้นในขณะที่ทำความสะอาด webRequest

if (webRequest.GetRequestStream() != null) 

จากนั้นรหัสจะออกโดยไม่ต้องจัดการข้อผิดพลาดที่เกิดขึ้นและทำให้เกิดปัญหาสำหรับวิธีการโทร

หวังว่านี่จะช่วยเป็นตัวอย่าง


1

การส่งข้อยกเว้นขณะที่ข้อยกเว้นอื่นทำงานอยู่จะส่งผลให้การยกเว้นครั้งแรกได้รับการแทนที่ด้วยข้อยกเว้นที่สอง (ภายหลัง)

นี่คือรหัสที่แสดงให้เห็นว่าเกิดอะไรขึ้น:

    public static void Main(string[] args)
    {
        try
        {
            try
            {
                throw new Exception("first exception");
            }
            finally
            {
                //try
                {
                    throw new Exception("second exception");
                }
                //catch (Exception)
                {
                    //throw;
                }
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }
    }
  • เรียกใช้รหัสและคุณจะเห็น "ข้อยกเว้นที่สอง"
  • ยกเลิกการคอมเม้นต์คำสั่ง try และ catch แล้วคุณจะเห็น "ข้อยกเว้นแรก"
  • ยังไม่แสดงความเห็นโยน; คำสั่งและคุณจะเห็น "ข้อยกเว้นที่สอง" อีกครั้ง

เป็นที่น่าสังเกตว่าเป็นไปได้สำหรับการล้างข้อยกเว้น "รุนแรง" ซึ่งจะติดอยู่นอกบล็อกรหัสเฉพาะเพื่อโยนข้อยกเว้นที่ถูกจับและจัดการภายใน การใช้ตัวกรองข้อยกเว้น (มีใน vb.net แต่ไม่ใช่ C #) คุณสามารถตรวจสอบเงื่อนไขนี้ได้ มีจำนวนไม่มากที่รหัสสามารถทำเพื่อ "จัดการ" ได้ แต่ถ้ามีใครใช้กรอบการบันทึกแบบใด ๆ มันก็คุ้มค่าที่จะบันทึก วิธีการ C ++ ของการมีข้อยกเว้นที่เกิดขึ้นในการทำความสะอาดทริกเกอร์การล่มสลายของระบบน่าเกลียด แต่การที่มีข้อยกเว้นหายไปนั้นเป็นเรื่องที่น่ารำคาญมาก
supercat

1

หลายเดือนก่อนฉันยังต้องเผชิญกับบางสิ่งเช่นนี้

    private  void RaiseException(String errorMessage)
    {
        throw new Exception(errorMessage);
    }

    private  void DoTaskForFinally()
    {
        RaiseException("Error for finally");
    }

    private  void DoTaskForCatch()
    {
        RaiseException("Error for catch");
    }

    private  void DoTaskForTry()
    {
        RaiseException("Error for try");
    }


        try
        {
            /*lacks the exception*/
            DoTaskForTry();
        }
        catch (Exception exception)
        {
            /*lacks the exception*/
            DoTaskForCatch();
        }
        finally
        {
            /*the result exception*/
            DoTaskForFinally();
        }

เพื่อแก้ปัญหาดังกล่าวฉันทำคลาสยูทิลิตี้เช่น

class ProcessHandler : Exception
{
    private enum ProcessType
    {
        Try,
        Catch,
        Finally,
    }

    private Boolean _hasException;
    private Boolean _hasTryException;
    private Boolean _hasCatchException;
    private Boolean _hasFinnallyException;

    public Boolean HasException { get { return _hasException; } }
    public Boolean HasTryException { get { return _hasTryException; } }
    public Boolean HasCatchException { get { return _hasCatchException; } }
    public Boolean HasFinnallyException { get { return _hasFinnallyException; } }
    public Dictionary<String, Exception> Exceptions { get; private set; } 

    public readonly Action TryAction;
    public readonly Action CatchAction;
    public readonly Action FinallyAction;

    public ProcessHandler(Action tryAction = null, Action catchAction = null, Action finallyAction = null)
    {

        TryAction = tryAction;
        CatchAction = catchAction;
        FinallyAction = finallyAction;

        _hasException = false;
        _hasTryException = false;
        _hasCatchException = false;
        _hasFinnallyException = false;
        Exceptions = new Dictionary<string, Exception>();
    }


    private void Invoke(Action action, ref Boolean isError, ProcessType processType)
    {
        try
        {
            action.Invoke();
        }
        catch (Exception exception)
        {
            _hasException = true;
            isError = true;
            Exceptions.Add(processType.ToString(), exception);
        }
    }

    private void InvokeTryAction()
    {
        if (TryAction == null)
        {
            return;
        }
        Invoke(TryAction, ref _hasTryException, ProcessType.Try);
    }

    private void InvokeCatchAction()
    {
        if (CatchAction == null)
        {
            return;
        }
        Invoke(TryAction, ref _hasCatchException, ProcessType.Catch);
    }

    private void InvokeFinallyAction()
    {
        if (FinallyAction == null)
        {
            return;
        }
        Invoke(TryAction, ref _hasFinnallyException, ProcessType.Finally);
    }

    public void InvokeActions()
    {
        InvokeTryAction();
        if (HasTryException)
        {
            InvokeCatchAction();
        }
        InvokeFinallyAction();

        if (HasException)
        {
            throw this;
        }
    }
}

และใช้แบบนี้

try
{
    ProcessHandler handler = new ProcessHandler(DoTaskForTry, DoTaskForCatch, DoTaskForFinally);
    handler.InvokeActions();
}
catch (Exception exception)
{
    var processError = exception as ProcessHandler;
    /*this exception contains all exceptions*/
    throw new Exception("Error to Process Actions", exception);
}

แต่ถ้าคุณต้องการใช้พารามิเตอร์และประเภทผลตอบแทนนั่นเป็นเรื่องอื่น


1
public void MyMethod()
{
   try
   {
   }
   catch{}
   finally
   {
      CodeA
   }
   CodeB
}

วิธีจัดการข้อยกเว้นโดย CodeA และ CodeB นั้นเหมือนกัน

ข้อยกเว้นที่ถูกโยนในfinallyบล็อกไม่มีอะไรพิเศษให้ถือว่าเป็นข้อยกเว้นการขว้างด้วยรหัส B


คุณสามารถทำอย่างละเอียด? คุณหมายถึงอะไรโดยมีข้อยกเว้นเหมือนกัน?
Dirk Vollmar

1

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

ไม่ว่าจะมีข้อยกเว้นหรือไม่ก็ตาม "block" รับประกันว่าจะสามารถใช้งานได้

  1. หากมีการดำเนินการบล็อก "ในที่สุด" หลังจากข้อยกเว้นเกิดขึ้นในบล็อกลอง

  2. และหากข้อยกเว้นนั้นไม่ได้รับการจัดการ

  3. และในที่สุดหากบล็อกพ่นข้อยกเว้น

จากนั้นข้อยกเว้นดั้งเดิมที่เกิดขึ้นในบล็อกลองจะหายไป

public class Exception
{
    public static void Main()
    {
        try
        {
            SomeMethod();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

    public static void SomeMethod()
    {
        try
        {
            // This exception will be lost
            throw new Exception("Exception in try block");
        }
        finally
        {
            throw new Exception("Exception in finally block");
        }
    }
} 

บทความที่ดีสำหรับรายละเอียด


-1

มันส่งข้อยกเว้น;) คุณสามารถตรวจจับข้อยกเว้นนั้นในประโยคจับอื่น ๆ

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