Điều gì xảy ra nếu một khối cuối cùng ném một ngoại lệ?


266

Nếu một khối cuối cùng ném một ngoại lệ, chính xác thì chuyện gì sẽ xảy ra?

Cụ thể, điều gì xảy ra nếu ngoại lệ được ném giữa chừng qua một khối cuối cùng. Phần còn lại của câu lệnh (sau) trong khối này có được gọi không?

Tôi biết rằng các ngoại lệ sẽ lan truyền lên trên.


8
Tại sao không thử nó? Nhưng với những thứ này, thứ tôi thích nhất là trở về trước cuối cùng và sau đó trả lại một thứ khác từ khối cuối cùng. :)
ANeves

8
Tất cả các câu lệnh trong một khối cuối cùng phải thực thi. Nó không thể có sự trở lại. msdn.microsoft.com/en-us/l Library / 0hbbzekw (VS.80) .aspx
Tim Scarborough

Câu trả lời:


419

Nếu một khối cuối cùng ném một ngoại lệ chính xác những gì xảy ra?

Ngoại lệ đó lan truyền ra ngoài và sẽ (có thể) được xử lý ở mức cao hơn.

Khối cuối cùng của bạn sẽ không được hoàn thành ngoài điểm ném ngoại lệ.

Nếu khối cuối cùng được thực thi trong quá trình xử lý một ngoại lệ trước đó thì ngoại lệ đầu tiên đó sẽ bị mất.

Đặc tả ngôn ngữ C # 4 § 8,9.5: Nếu khối cuối cùng ném ngoại lệ khác, việc xử lý ngoại lệ hiện tại bị chấm dứt.


9
Trừ khi nó là một ThreadAbortException, sau đó toàn bộ khối cuối cùng sẽ được hoàn thành đầu tiên, vì nó là một phần quan trọng.
Dmytro Shevchenko

1
@Shedal - bạn đúng nhưng chỉ áp dụng cho "một số trường hợp ngoại lệ không đồng bộ nhất định", tức là ThreadAdortException. Đối với mã 1 luồng thông thường, câu trả lời của tôi giữ.
Henk Holterman

"Ngoại lệ đầu tiên bị mất" - điều đó thực sự rất thất vọng, tình cờ tôi tìm thấy các đối tượng IDis Dùng ngoại lệ trong Dispose (), dẫn đến ngoại lệ bị mất trong mệnh đề "sử dụng".
Alex Burtsev

"Tôi tìm thấy các đối tượng IDis Dùng một ngoại lệ trong Dispose ()" - thật kỳ lạ khi nói ít nhất. Đọc trên MSDN: TRÁNH ném một ngoại lệ từ bên trong Vứt bỏ (bool) ngoại trừ ...
Henk Holterman

1
@HenkHolterman: Lỗi đầy đĩa không phổ biến trên đĩa cứng chính được kết nối trực tiếp, nhưng đôi khi các chương trình ghi tệp vào đĩa có thể tháo rời hoặc nối mạng; vấn đề có thể phổ biến hơn nhiều với những người. Nếu ai đó rút ra một thanh USB trước khi tệp được ghi đầy đủ, sẽ tốt hơn là nói cho họ biết ngay lập tức hơn là đợi cho đến khi họ đến nơi họ đang đi và thấy tệp bị hỏng. Mang lại lỗi trước đó khi có một hành vi có thể cảm nhận được, nhưng khi không có lỗi sớm hơn, tốt hơn là báo cáo vấn đề hơn là không báo cáo.
supercat

101

Đối với những câu hỏi như thế này, tôi thường mở một dự án ứng dụng bảng điều khiển trống trong Visual Studio và viết một chương trình mẫu nhỏ:

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

Khi bạn chạy chương trình, bạn sẽ thấy thứ tự chính xác trong đó catchfinallycác khối được thực thi. Xin lưu ý rằng mã trong khối cuối cùng sau khi ném ngoại lệ sẽ không được thực thi (thực tế, trong chương trình mẫu này, Visual Studio thậm chí sẽ cảnh báo bạn rằng nó đã phát hiện mã không thể truy cập được):

Xử lý khối bắt bên trong ngoại lệ ném từ khối thử.
Khối cuối cùng bên trong
Xử lý ngoại lệ khối bắt bên ngoài ném từ khối cuối cùng.
Bên ngoài khối cuối cùng

Ghi chú bổ sung

Như Michael Damatov đã chỉ ra, một ngoại lệ từ trykhối sẽ bị "ăn" nếu bạn không xử lý nó trong catchkhối (bên trong) . Trong thực tế, trong ví dụ trên, ngoại lệ ném lại không xuất hiện trong khối bắt bên ngoài. Để làm cho điều đó rõ ràng hơn nữa, nhìn vào mẫu được sửa đổi một chút sau đây:

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

Như bạn có thể thấy từ đầu ra, ngoại lệ bên trong là "mất" (tức là bị bỏ qua):

Khối cuối cùng bên trong
Xử lý ngoại lệ khối bắt bên ngoài ném từ khối cuối cùng.
Bên ngoài khối cuối cùng

2
Bởi vì bạn đã ghi nhớ Ngoại lệ trong phần bắt bên trong của mình, 'Khối cuối cùng bên trong' sẽ không bao giờ đạt được trong ví dụ này
Theofanis Pantelides

4
@Theofanis Pantelides: Không, một finallykhối sẽ (hầu như) luôn luôn được thực thi, điều này cũng giữ trong trường hợp này đối với khối cuối cùng bên trong (chỉ cần tự thử chương trình mẫu (Khối cuối cùng sẽ không được thực thi trong trường hợp không thể phục hồi ngoại lệ, ví dụ như một EngineExecutionException, nhưng trong trường hợp như vậy, chương trình của bạn sẽ chấm dứt ngay lập tức dù thế nào đi nữa.
Dirk Vollmar

1
Mặc dù vậy, tôi không thấy vai trò của cú ném trong lần bắt đầu đầu tiên của đoạn mã đầu tiên của bạn là gì. Tôi đã thử với và không có nó với một ứng dụng giao diện điều khiển, không tìm thấy sự khác biệt nào.
JohnPan 18/03/2016

@johnpan: Vấn đề là chỉ ra rằng khối cuối cùng luôn luôn thực thi, ngay cả khi cả khối thử và bắt khối đều ném ngoại lệ. Thực sự không có sự khác biệt trong đầu ra giao diện điều khiển.
Dirk Vollmar 18/03/2016

10

Nếu có một ngoại lệ đang chờ xử lý (khi trykhối có finallynhưng không có catch), ngoại lệ mới sẽ thay thế ngoại lệ đó.

Nếu không có ngoại lệ đang chờ xử lý, nó hoạt động giống như ném một ngoại lệ bên ngoài finallykhối.


Một ngoại lệ cũng có thể được cấp phát nếu có một hợp catchkhối (tái) ném một ngoại lệ.
stakx - không còn đóng góp


3

Đoạn mã nhanh (và khá rõ ràng) để lưu "ngoại lệ gốc" (ném theo trykhối) và hy sinh "ngoại lệ cuối cùng" (ném theo finallykhối), trong trường hợp bản gốc quan trọng hơn đối với bạn:

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

Khi mã ở trên được thực thi, "Ngoại lệ gốc" sẽ truyền lên ngăn xếp cuộc gọi và "Cuối cùng là ngoại lệ" bị mất.


2

Tôi đã phải làm điều này để bắt lỗi khi cố gắng đóng một luồng không bao giờ được mở vì một ngoại lệ.

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

nếu webRequest được tạo nhưng đã xảy ra lỗi kết nối trong quá trình

using (var sw = webRequest.GetRequestStream())

sau đó cuối cùng sẽ bắt gặp một ngoại lệ khi cố gắng đóng các kết nối mà nó nghĩ là mở vì webRequest đã được tạo.

Nếu cuối cùng không có thử bắt bên trong, mã này sẽ gây ra ngoại lệ chưa được xử lý trong khi dọn dẹp webRequest

if (webRequest.GetRequestStream() != null) 

từ đó mã sẽ thoát mà không xử lý đúng lỗi đã xảy ra và do đó gây ra sự cố cho phương thức gọi.

Hy vọng điều này sẽ giúp làm ví dụ


1

Ném một ngoại lệ trong khi một ngoại lệ khác đang hoạt động sẽ dẫn đến ngoại lệ đầu tiên được thay thế bằng ngoại lệ thứ hai (sau này).

Đây là một số mã minh họa những gì xảy ra:

    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);
        }
    }
  • Chạy mã và bạn sẽ thấy "ngoại lệ thứ hai"
  • Bỏ ghi chú các câu lệnh thử và bắt và bạn sẽ thấy "ngoại lệ đầu tiên"
  • Cũng không chú ý đến việc ném; tuyên bố và bạn sẽ thấy "ngoại lệ thứ hai" một lần nữa.

Điều đáng chú ý là có thể dọn sạch một ngoại lệ "nghiêm trọng" mà chỉ có thể bị bắt bên ngoài một khối mã cụ thể để ném một ngoại lệ bị bắt và xử lý trong đó. Sử dụng các bộ lọc ngoại lệ (có sẵn trong vb.net, mặc dù không phải C #), có thể phát hiện tình trạng này. Không có nhiều thứ mà mã có thể làm để "xử lý" nó, mặc dù nếu một người đang sử dụng bất kỳ loại khung ghi nhật ký nào thì điều đó gần như chắc chắn đáng để đăng nhập. Cách tiếp cận C ++ về việc có các ngoại lệ xảy ra trong quá trình dọn dẹp, một hệ thống bị xáo trộn là xấu, nhưng có các ngoại lệ biến mất là IMHO gớm ghiếc.
supercat

1

Vài tháng trước tôi cũng phải đối mặt với một cái gì đó như thế này,

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

Để giải quyết vấn đề như vậy tôi đã tạo một lớp tiện ích như

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

Và được sử dụng như thế này

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

nhưng nếu bạn muốn sử dụng paramaters và trả về các loại đó là một câu chuyện khác


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

Cách xử lý các ngoại lệ do CodeA và CodeB xử lý là như nhau.

Một ngoại lệ được ném trong một finallykhối không có gì đặc biệt, coi nó là ngoại lệ được ném bởi mã B.


Bạn có thể giải thích? Bạn có ý nghĩa gì với các ngoại lệ là như nhau?
Dirk Vollmar

1

Ngoại lệ lan truyền lên, và nên được xử lý ở mức cao hơn. Nếu ngoại lệ không được xử lý ở cấp cao hơn, ứng dụng sẽ gặp sự cố. Việc thực thi khối "cuối cùng" dừng tại điểm ném ngoại lệ.

Bất kể có một ngoại lệ hay không, khối "cuối cùng" được đảm bảo để thực thi.

  1. Nếu khối "cuối cùng" đang được thực thi sau khi xảy ra ngoại lệ trong khối thử,

  2. và nếu ngoại lệ đó không được xử lý

  3. và nếu khối cuối cùng ném một ngoại lệ

Sau đó, ngoại lệ ban đầu xảy ra trong khối thử bị mất.

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

Bài viết tuyệt vời để biết chi tiết


-1

Nó ném một ngoại lệ;) Bạn có thể bắt ngoại lệ đó trong một số mệnh đề bắt khác.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.