Sẽ mã trong lệnh tuyên bố Cuối cùng nếu tôi trả về giá trị trong khối Thử?


237

Tôi đang xem xét một số mã cho một người bạn và nói rằng anh ta đang sử dụng câu lệnh return trong khối thử cuối cùng. Liệu mã trong phần Cuối cùng vẫn kích hoạt mặc dù phần còn lại của khối thử không?

Thí dụ:

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


Bị xử lý ở đây có nghĩa là: bị bắt. Ngay cả một khối bắt trống trong xử lý toàn cầu của bạn là đủ. Ngoài ra, có những trường hợp ngoại lệ không thể xử lý : StackOverflowException, ExecutionEngineExceptionlà một số trong số đó. Và vì chúng không thể được xử lý, nên finallysẽ không chạy.
Abel

8
@Abel: Có vẻ như bạn đang nói về một tình huống khác. Câu hỏi này là về việc trở lại trong một trykhối. Không có gì về việc hủy bỏ chương trình đột ngột.
Jon Skeet

6
@Abel: Tôi không chắc ý của bạn là gì khi "trở về sau một ngoại lệ", nhưng dường như đó không phải là điều được hỏi ở đây. Nhìn vào mã - câu lệnh đầu tiên của trykhối là một returncâu lệnh. (Tuyên bố thứ hai của khối đó là không thể truy cập được và sẽ tạo cảnh báo.)
Jon Skeet

4
@Abel: Thật vậy, và nếu câu hỏi là "Mã sẽ trong một tuyên bố cuối cùng luôn luôn thực thi trong mọi tình huống" có thể có liên quan. Nhưng đó không phải là những gì đang được hỏi.
Jon Skeet

Câu trả lời:


265

Câu trả lời đơn giản: Có.


9
@Edi: Ừm, tôi không thấy chủ đề nền nào phải làm với nó. Về cơ bản, trừ khi toàn bộ quá trình hủy bỏ, finallykhối sẽ thực thi.
Jon Skeet

3
Câu trả lời dài là bạn sẽ nhất thiết phải có một khối cuối cùng để chạy nếu có điều gì đó thảm khốc đã xảy ra, chẳng hạn như lỗi tràn bộ nhớ, ngoại lệ bộ nhớ, một sự cố nghiêm trọng nào đó hoặc nếu ai đó tháo máy của bạn đúng lúc . Nhưng đối với tất cả ý định và mục đích, trừ khi bạn đang làm điều gì đó rất sai, khối cuối cùng sẽ luôn kích hoạt.
Andrew Rollings

Nếu mã trước khi thử thất bại do một số lý do tôi đã nhận thấy rằng cuối cùng vẫn được thực thi. Trong trường hợp đó, bạn có thể thêm một điều kiện để cuối cùng thỏa mãn các tiêu chí để thực thi cuối cùng chỉ khi mã được thực thi thành công
kjosh

200

Thông thường, có. Phần cuối cùng được đảm bảo để thực hiện bất cứ điều gì xảy ra bao gồm ngoại lệ hoặc tuyên bố trả về. Một ngoại lệ cho quy tắc này là một ngoại lệ không đồng bộ xảy ra trên luồng ( OutOfMemoryException, StackOverflowException).

Để tìm hiểu thêm về các ngoại lệ không đồng bộ và mã đáng tin cậy trong các tình huống đó, hãy đọc về các vùng thực thi bị ràng buộc .


154

Đây là một bài kiểm tra nhỏ:

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

Kết quả là:

before
finally
return
after

10
@CodeBlend: Nó phải thực hiện khi WriteLinekết quả của cuộc gọi phương thức thực sự được thực hiện. Trong trường hợp này, returncuộc gọi đặt kết quả phương thức, cuối cùng ghi vào bàn điều khiển - trước khi phương thức thoát. Sau đó, WriteLinetrong phương thức Main phun ra văn bản từ cuộc gọi trở lại.
NotMe

38

Trích dẫn từ MSDN

cuối cùng được sử dụng để đảm bảo một khối lệnh thực thi mã bất kể khối thử trước đó được thoát như thế nào .


3
Chà, ngoài các trường hợp đặc biệt của Mehrdad, cuối cùng cũng sẽ không được gọi nếu bạn đang gỡ lỗi trong một khối thử, và sau đó dừng gỡ lỗi :). Không có gì trong cuộc sống được đảm bảo, dường như.
StuartLC

1
Không hoàn toàn, trích dẫn từ MSDN : Tuy nhiên, nếu ngoại lệ không được xử lý, việc thực thi khối cuối cùng phụ thuộc vào cách hoạt động thư giãn ngoại lệ được kích hoạt. Điều đó, đến lượt nó, phụ thuộc vào cách máy tính của bạn được thiết lập..
Abel

1
@Abel làm cho một điểm quan trọng ở đây. Mọi người đều có thể thấy cách một khối cuối cùng không được thực thi nếu ví dụ chương trình bị hủy bỏ thông qua trình quản lý tác vụ. Nhưng câu trả lời này là, vì nó đứng, hoàn toàn sai: finallytrên thực tế không đảm bảo gì ngay cả đối với các chương trình hoàn toàn bình thường (đã xảy ra để ném ngoại lệ này).
Peter - Phục hồi Monica

19

Nói chung là có, cuối cùng sẽ chạy.

Đối với ba kịch bản sau đây, cuối cùng sẽ LUÔN chạy:

  1. Không có ngoại lệ xảy ra
  2. Ngoại lệ đồng bộ (ngoại lệ xảy ra trong luồng chương trình bình thường).
    Điều này bao gồm các ngoại lệ tuân thủ CLS xuất phát từ ngoại lệ System.Exception và không tuân thủ CLS, không xuất phát từ System.Exception. Các ngoại lệ không tuân thủ CLS được tự động bao bọc bởi RuntimeWrappingException. C # không thể đưa ra các ngoại lệ khiếu nại không phải CLS, nhưng các ngôn ngữ như C ++ thì có thể. C # có thể được gọi vào mã được viết bằng ngôn ngữ có thể đưa ra các ngoại lệ không tuân thủ CLS.
  3. ThreadAdortException không đồng bộ
    Kể từ .NET 2.0, một ThreadAdortException sẽ không còn ngăn được cuối cùng chạy. ThreadAdortException bây giờ được kéo lên trước hoặc sau cuối cùng. Cuối cùng sẽ luôn luôn chạy và sẽ không bị gián đoạn bởi một hủy bỏ luồng, miễn là thử thực sự được nhập trước khi hủy bỏ luồng xảy ra.

Kịch bản sau đây, cuối cùng sẽ không chạy:

StackOverflowException không đồng bộ.
Kể từ .NET 2.0, tràn ngăn xếp sẽ khiến quá trình chấm dứt. Cuối cùng sẽ không được chạy, trừ khi áp dụng một ràng buộc hơn nữa để biến cuối cùng thành CER (Vùng thực thi bị ràng buộc). CER không nên được sử dụng trong mã người dùng chung. Chúng chỉ nên được sử dụng khi điều quan trọng là mã dọn dẹp luôn chạy - sau khi tất cả quá trình sẽ tắt khi tràn ngăn xếp và do đó tất cả các đối tượng được quản lý sẽ được dọn sạch theo mặc định. Do đó, nơi duy nhất CER có liên quan là dành cho các tài nguyên được phân bổ ngoài quy trình, ví dụ: các thẻ điều khiển không được quản lý.

Thông thường, mã không được quản lý được bao bọc bởi một số lớp được quản lý trước khi được sử dụng bởi mã người dùng. Lớp trình bao bọc được quản lý thường sẽ sử dụng SafeHandle để bọc tay cầm không được quản lý. SafeHandle thực hiện một bộ hoàn thiện quan trọng và phương thức Phát hành được chạy trong CER để đảm bảo thực thi mã dọn dẹp. Vì lý do này, bạn không nên thấy các CER rải rác mã người dùng.

Vì vậy, thực tế là cuối cùng không chạy trên StackOverflowException sẽ không ảnh hưởng đến mã người dùng, vì dù sao quá trình sẽ chấm dứt. Nếu bạn có một số trường hợp cạnh mà bạn cần phải dọn sạch một số tài nguyên không được quản lý, bên ngoài SafeHandle hoặc CriticalFinalizerObject, thì hãy sử dụng CER như sau; nhưng xin lưu ý, đây là cách thực hành tồi - khái niệm không được quản lý nên được trừu tượng hóa thành một lớp được quản lý và (các) SafeHandle thích hợp theo thiết kế.

ví dụ,

// 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.
}

Xin lưu ý rằng ngay cả CER sẽ không chạy trong trường hợp SOE. Tại thời điểm bạn viết bài này, tài liệu MSDN về CER bị sai / không đầy đủ. Một SOE sẽ kích hoạt FailFastnội bộ. Cách duy nhất tôi quản lý để bắt những thứ đó là bằng cách tùy chỉnh lưu trữ thời gian chạy CLR . Lưu ý rằng quan điểm của bạn vẫn hợp lệ đối với một số trường hợp ngoại lệ không đồng bộ khác.
Abel

10

Có một ngoại lệ rất quan trọng đối với điều này mà tôi chưa từng thấy được đề cập trong bất kỳ câu trả lời nào khác, và điều đó (sau khi lập trình trong C # trong 18 năm) tôi không thể tin rằng mình không biết.

Nếu bạn ném hoặc kích hoạt một ngoại lệ của bất kỳ loại nào trong catchkhối của bạn (không chỉ lạ StackOverflowExceptionsvà những thứ của ilk đó) và bạn không có toàn bộ try/catch/finallykhối bên trong một try/catchkhối khác , finallykhối của bạn sẽ không thực thi. Điều này dễ dàng được chứng minh - và nếu tôi đã không nhìn thấy nó, với tần suất tôi đã đọc rằng nó chỉ là những trường hợp góc nhỏ thực sự kỳ lạ có thể khiến một finallykhối không thực thi, tôi sẽ không tin điều đó.

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

Tôi chắc chắn có một lý do cho điều này, nhưng thật kỳ lạ là nó không được biết đến rộng rãi hơn. ( Ví dụ, nó được ghi chú ở đây , nhưng không phải ở đâu trong câu hỏi cụ thể này.)


Chà, finallykhối chỉ đơn giản là không bắt được catchkhối.
Peter - Tái lập Monica

7

Tôi nhận ra rằng tôi đến bữa tiệc muộn nhưng trong kịch bản (khác với ví dụ của OP) khi thực sự có một ngoại lệ được ném ra các trạng thái MSDN ( https://msdn.microsoft.com/en-us/l Library / zwc8s4fz.aspx ): "Nếu ngoại lệ không bị bắt, việc thực thi khối cuối cùng phụ thuộc vào vào việc hệ điều hành có chọn kích hoạt hoạt động thư giãn ngoại lệ hay không."

Khối cuối cùng chỉ được đảm bảo thực thi nếu một số chức năng khác (chẳng hạn như Chính) tiếp tục ngăn xếp cuộc gọi bắt ngoại lệ. Chi tiết này thường không phải là vấn đề vì tất cả các môi trường thời gian chạy (CLR và HĐH) Các chương trình C # chạy trên hầu hết các tài nguyên mà một quy trình sở hữu khi nó thoát ra (xử lý tệp, v.v.). Trong một số trường hợp, điều này có thể rất quan trọng: Một hoạt động cơ sở dữ liệu đang được tiến hành một nửa mà bạn muốn cam kết. thư giãn; hoặc một số kết nối từ xa có thể không được hệ điều hành tự động đóng và sau đó chặn máy chủ.


3

Đúng. Trên thực tế, đó là điểm chính của một tuyên bố cuối cùng. Trừ khi có điều gì đó thảm khốc xảy ra (hết bộ nhớ, rút ​​phích cắm máy tính, v.v.), câu lệnh cuối cùng sẽ luôn luôn được thực thi.


1
Tôi xin không đồng ý. Cf. câu trả lời của tôi. Một ngoại lệ hoàn toàn bình thường trong khối thử là đủ để bỏ qua finallykhối nếu ngoại lệ đó không bao giờ bị bắt. Điều đó làm tôi ngạc nhiên ;-).
Peter - Tái lập Monica

@ PeterA.Schneider - Điều đó thật thú vị. Tất nhiên, ý nghĩa của điều đó không thực sự thay đổi nhiều. Nếu không có gì trong ngăn xếp cuộc gọi sẽ bắt ngoại lệ, thì điều đó có nghĩa là (nếu tôi hiểu chính xác) rằng quy trình sắp bị chấm dứt. Vì vậy, nó tương tự như tình huống kéo-cắm. Tôi đoán rằng điều đáng nói ở đây là bạn phải luôn có một ngoại lệ cấp cao nhất hoặc chưa được xử lý.
Jeffrey L Whitledge

Chính xác, đó là những gì tôi hiểu là tốt. Tôi thấy rằng đáng ngạc nhiên, quá. Đúng: Các tài nguyên phổ biến nhất sẽ được phát hành tự động, đặc biệt là bộ nhớ và các tệp đã mở. Nhưng các tài nguyên mà HĐH không biết về - kết nối máy chủ hoặc cơ sở dữ liệu làm ví dụ - có thể vẫn mở ở phía xa vì chúng chưa bao giờ được tắt đúng cách; ổ cắm cứ kéo dài, v.v ... Tôi đoán rằng thật khôn ngoan khi có một bộ xử lý ngoại lệ cấp cao nhất, vâng.
Peter - Tái lập Monica


2

cuối cùng sẽ không chạy trong trường hợp nếu bạn thoát khỏi ứng dụng bằng System.exit (0); như trong

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

kết quả sẽ chỉ là: thử


4
Bạn đang trả lời sai ngôn ngữ tôi cho rằng, câu hỏi là về c#, nhưng điều này dường như là Java. Và bên cạnh đó, trong hầu hết các trường hợp System.exit()là một gợi ý về một thiết kế tồi :-)
z00l

0

finallyTuy nhiên, 99% kịch bản sẽ được đảm bảo rằng mã bên trong khối sẽ chạy, tuy nhiên, hãy nghĩ đến kịch bản này: Bạn có một luồng có một khối try-> finally(không catch) và bạn nhận được một ngoại lệ chưa được xử lý trong luồng đó. Trong trường hợp này, luồng sẽ thoát và finallykhối của nó sẽ không được thực thi (ứng dụng có thể tiếp tục chạy trong trường hợp này)

Kịch bản này khá hiếm, nhưng chỉ cho thấy rằng câu trả lời không phải là LUÔN "Có", hầu hết thời gian là "Có" và đôi khi, trong điều kiện hiếm hoi, "Không".


0

Mục đích chính của khối cuối cùng là thực thi bất cứ điều gì được viết bên trong nó. Nó không nên phụ thuộc vào bất cứ điều gì xảy ra trong thử hoặc bắt. Tuy nhiên, với System.En Môi trường.Exit (1) ứng dụng sẽ thoát mà không chuyển sang dòng mã tiếp theo.

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.