Akankah kode dalam pernyataan Akhirnya diaktifkan jika saya mengembalikan nilai dalam blok Coba?

237

Saya sedang meninjau beberapa kode untuk seorang teman dan mengatakan bahwa dia menggunakan pernyataan pengembalian di dalam blok coba-akhirnya. Apakah kode di bagian Akhirnya masih menyala meskipun sisa blok percobaan tidak?

Contoh:

public bool someMethod()
{
  try
  {
    return true;
    throw new Exception("test"); // doesn't seem to get executed
  }
  finally
  {
    //code in question
  }
}
JamesEggers
sumber
Ditangani di sini berarti: tertangkap. Bahkan block catch kosong di handler global Anda sudah cukup. Juga, ada pengecualian yang tidak dapat ditangani: StackOverflowException, ExecutionEngineExceptionadalah beberapa dari mereka. Dan karena mereka tidak dapat ditangani, finallytidak akan berjalan.
Abel
8
@ Bel: Kamu sepertinya berbicara tentang situasi yang berbeda. Pertanyaan ini tentang kembali dalam satu tryblok. Tidak ada yang tentang aborsi program yang tiba-tiba.
Jon Skeet
6
@ Bel: Saya tidak yakin apa yang Anda maksud dengan "kembali setelah pengecualian", tapi sepertinya itu bukan yang ditanyakan di sini. Lihatlah kode - pernyataan pertama dari tryblok adalah returnpernyataan. (Pernyataan kedua dari blok itu tidak dapat dijangkau dan akan menghasilkan peringatan.)
Jon Skeet
4
@ Bel: Memang, dan jika pertanyaannya adalah "Akankah kode dalam pernyataan akhirnya selalu dijalankan dalam setiap situasi" itu akan relevan. Tapi bukan itu yang ditanyakan.
Jon Skeet

Jawaban:

265

Jawaban sederhana: Ya.

Andrew Rollings
sumber
9
@ Edi: Um, saya tidak melihat apa kaitannya dengan latar belakang. Pada dasarnya, kecuali seluruh proses dibatalkan, finallyblok akan dieksekusi.
Jon Skeet
3
Jawaban panjangnya adalah Anda tidak akan selalu mendapatkan blok terakhir untuk dijalankan jika sesuatu bencana terjadi, seperti Stack Overflow, Out of Memory pengecualian, crash keras dari beberapa jenis, atau jika seseorang mencabut mesin Anda pada waktu yang tepat. . Tetapi untuk semua maksud dan tujuan, kecuali jika Anda melakukan sesuatu yang sangat salah, blok akhirnya akan selalu menyala.
Andrew Rollings
Jika kode sebelum coba gagal karena beberapa alasan saya perhatikan bahwa akhirnya masih dijalankan. Dalam hal ini Anda dapat menambahkan kondisi untuk yang akhirnya memenuhi kriteria untuk dieksekusi akhirnya hanya ketika kode di bawah dieksekusi berhasil
kjosh
200

Biasanya ya. Bagian terakhir dijamin untuk mengeksekusi apa pun yang terjadi termasuk pengecualian atau pernyataan pengembalian. Pengecualian untuk aturan ini adalah pengecualian asinkron yang terjadi pada utas ( OutOfMemoryException, StackOverflowException).

Untuk mempelajari lebih lanjut tentang pengecualian async dan kode yang dapat diandalkan dalam situasi itu, baca tentang wilayah eksekusi terbatas .

Mehrdad Afshari
sumber
154

Inilah sedikit tes:

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

Hasilnya adalah:

before
finally
return
after
Jon B
sumber
10
@ KodeBlend: Ini ada hubungannya dengan ketika WriteLineuntuk hasil dari pemanggilan metode benar-benar dilakukan. Dalam hal ini returnpanggilan menetapkan hasil metode, akhirnya menulis ke konsol - sebelum metode keluar. Kemudian WriteLinedalam metode Utama memuntahkan teks dari panggilan kembali.
NotMe
38

Mengutip dari MSDN

akhirnya digunakan untuk menjamin blok pernyataan dari kode yang dieksekusi terlepas dari bagaimana blok percobaan sebelumnya keluar .

Perpetualcoder
sumber
3
Nah, selain kasus luar biasa Mehrdad, akhirnya juga tidak akan dipanggil jika Anda debugging di blok try, dan kemudian berhenti debugging :). Sepertinya tidak ada kehidupan yang dijamin.
StuartLC
1
Tidak cukup, mengutip dari MSDN : Namun, jika pengecualian tidak ditangani, eksekusi blok akhirnya tergantung pada bagaimana operasi pelepasan pengecualian dipicu. Itu, pada gilirannya, tergantung pada bagaimana komputer Anda diatur. .
Abel
1
@ Bel membuat poin penting di sini. Semua orang dapat melihat bagaimana akhirnya blok tidak dieksekusi jika misalnya program dibatalkan melalui pengelola tugas. Tetapi jawaban ini, seperti kelihatannya, salah: finallypada kenyataannya tidak ada jaminan bahkan untuk program yang sangat biasa (yang kebetulan membuang pengecualian ini).
Peter - Reinstate Monica
19

Umumnya ya, akhirnya akan berjalan.

Untuk tiga skenario berikut, akhirnya akan SELALU berjalan:

  1. Tidak ada pengecualian
  2. Pengecualian sinkron (pengecualian yang terjadi dalam aliran program normal).
    Ini termasuk pengecualian yang sesuai dengan CLS yang berasal dari System.Exception dan pengecualian yang tidak sesuai dengan CLS, yang tidak berasal dari System.Exception. Pengecualian yang tidak memenuhi CLS secara otomatis dibungkus oleh RuntimeWrappedException. C # tidak bisa melempar pengecualian keluhan non-CLS, tetapi bahasa seperti C ++ bisa. C # dapat memanggil kode yang ditulis dalam bahasa yang dapat membuang pengecualian yang tidak sesuai dengan CLS.
  3. Asynchronous ThreadAbortException
    Pada. NET 2.0, sebuah ThreadAbortException tidak lagi mencegah akhirnya menjalankan. ThreadAbortException sekarang diangkat ke sebelum atau sesudah akhirnya. Akhirnya akan selalu berjalan dan tidak akan terganggu oleh utas yang dibatalkan, selama upaya tersebut benar-benar dimasukkan sebelum utas yang dibatalkan terjadi.

Skenario berikut, akhirnya tidak akan berjalan:

StackOverflowException Asynchronous.
Pada. NET 2.0 stack overflow akan menyebabkan proses berakhir. Akhirnya tidak akan dijalankan, kecuali kendala lebih lanjut diterapkan untuk membuat akhirnya CER (Constrained Execution Region). CER tidak boleh digunakan dalam kode pengguna umum. Mereka hanya boleh digunakan di mana sangat penting bahwa kode pembersihan selalu dijalankan - setelah semua proses ditutup pada stack overflow dan semua objek yang dikelola karenanya akan dibersihkan secara default. Dengan demikian, satu-satunya tempat CER harus relevan adalah untuk sumber daya yang dialokasikan di luar proses, misalnya, pegangan yang tidak dikelola.

Biasanya, kode yang tidak dikelola dibungkus oleh beberapa kelas terkelola sebelum dikonsumsi oleh kode pengguna. Kelas pembungkus yang dikelola biasanya akan menggunakan SafeHandle untuk membungkus pegangan yang tidak dikelola. SafeHandle mengimplementasikan finalizer kritis, dan metode Release yang dijalankan dalam CER untuk menjamin eksekusi kode pembersihan. Untuk alasan ini, Anda seharusnya tidak melihat CER berserakan melalui kode pengguna.

Jadi fakta bahwa akhirnya tidak berjalan di StackOverflowException seharusnya tidak berpengaruh pada kode pengguna, karena prosesnya akan tetap berakhir. Jika Anda memiliki beberapa kasus tepi di mana Anda perlu membersihkan beberapa sumber daya yang tidak dikelola, di luar SafeHandle atau CriticalFinalizerObject, kemudian gunakan CER sebagai berikut; tetapi harap dicatat, ini adalah praktik yang buruk - konsep yang tidak dikelola harus diabstraksi menjadi kelas yang dikelola dan desain SafeHandle yang sesuai.

misalnya,

// 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.
}
markn
sumber
Harap dicatat bahwa bahkan CER tidak akan berjalan dalam kasus SOE. Pada saat Anda menulis ini, dokumentasi MSDN pada CER salah / tidak lengkap. Sebuah BUMN akan memicu FailFastinternal. Satu-satunya cara saya berhasil menangkapnya adalah dengan menyesuaikan hosting runtime CLR . Perhatikan bahwa poin Anda masih valid untuk beberapa pengecualian asinkron lainnya.
Abel
10

Ada pengecualian yang sangat penting untuk ini yang belum saya lihat disebutkan dalam jawaban lain, dan yang (setelah pemrograman dalam C # selama 18 tahun) saya tidak percaya saya tidak tahu.

Jika Anda melempar atau memicu pengecualian apa pun di dalam catchblok Anda (bukan hanya aneh StackOverflowExceptionsdan sejenisnya), dan Anda tidak memiliki seluruh try/catch/finallyblok di dalam try/catchblok lain , finallyblok Anda tidak akan dieksekusi. Ini mudah ditunjukkan - dan jika saya tidak melihatnya sendiri, mengingat seberapa sering saya membaca bahwa itu hanya benar-benar aneh, kasus sudut kecil yang dapat menyebabkan finallyblok tidak dieksekusi, saya tidak akan percaya.

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

Saya yakin ada alasan untuk ini, tetapi aneh bahwa itu tidak diketahui secara luas. (Dicatat di sini misalnya, tetapi tidak di mana pun dalam pertanyaan khusus ini.)

Ken Smith
sumber
Yah, finallybloknya bukan untuk menangkap catchblok.
Peter - Pasang kembali Monica
7

Saya menyadari bahwa saya terlambat ke pesta tetapi dalam skenario (berbeda dari contoh OP) di mana memang ada pengecualian MSDN ( https://msdn.microsoft.com/en-us/library/zwc8s4fz.aspx ): "Jika pengecualian tidak ditangkap, eksekusi blok akhirnya tergantung pada apakah sistem operasi memilih untuk memicu operasi pelepasan pengecualian."

Blok akhirnya hanya dijamin untuk dieksekusi jika beberapa fungsi lain (seperti Main) lebih jauh dari tumpukan panggilan menangkap pengecualian. Detail ini biasanya bukan masalah karena semua lingkungan waktu berjalan (CLR dan OS) program C # berjalan pada sebagian besar sumber daya gratis yang dimiliki proses ketika keluar (menangani file dll.). Dalam beberapa kasus mungkin sangat penting: Operasi basis data setengah berjalan yang ingin Anda lakukan resp. beristirahat; atau koneksi jarak jauh yang mungkin tidak ditutup secara otomatis oleh OS dan kemudian memblokir server.

Peter - Pasang kembali Monica
sumber
3

Iya. Itu sebenarnya poin utama dari pernyataan terakhir. Kecuali jika sesuatu yang katestropik terjadi (kehabisan memori, komputer dicabut, dll.) Pernyataan terakhir harus selalu dijalankan.

Jeffrey L Whitledge
sumber
1
Saya mohon tidak setuju. Lih Jawabanku. Pengecualian yang sangat normal di blok percobaan sudah cukup untuk melewati finallyblok jika pengecualian itu tidak pernah tertangkap. Itu mengejutkan saya ;-).
Peter - Reinstate Monica
@ PeterA.Schneider - Itu menarik. Tentu saja implikasi itu tidak banyak berubah. Jika tidak ada tumpukan panggilan akan menangkap pengecualian, maka itu harus berarti (jika saya mengerti dengan benar) bahwa proses ini akan dihentikan. Jadi ini mirip dengan situasi cabut-colek. Saya kira takeaway dari ini adalah bahwa Anda harus selalu memiliki tangkapan tingkat atas atau tidak tertangani.
Jeffrey L Whitledge
Persis, itulah yang saya mengerti juga. Saya menemukan itu mengejutkan juga. Benar: Sumber daya yang paling umum akan dirilis secara otomatis, dalam memori tertentu dan file yang dibuka. Tetapi sumber daya yang tidak diketahui oleh OS - server terbuka atau koneksi database sebagai contoh - dapat tetap terbuka di sisi yang jauh karena tidak pernah ditutup dengan semestinya; soket tetap melekat, dll. Saya kira lebih baik memiliki pengendali pengecualian tingkat atas, ya.
Peter - Reinstate Monica
2

akhirnya tidak akan berjalan jika Anda keluar dari aplikasi menggunakan System.exit (0); seperti dalam

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

hasilnya hanya: coba

Hakuna Matata
sumber
4
Anda menjawab dalam bahasa yang salah saya kira, pertanyaannya adalah tentang c#, tetapi sepertinya ini Java. Dan di samping itu, dalam banyak kasus System.exit()adalah petunjuk dari desain yang buruk :-)
z00l
0

99% dari skenario akan dijamin bahwa kode di dalam finallyblok akan berjalan, namun pikirkan skenario ini: Anda memiliki utas yang memiliki try-> finallyblok (tidak catch) dan Anda mendapatkan pengecualian yang tidak tertangani dalam utas itu. Dalam hal ini, utas akan keluar dan finallybloknya tidak akan dieksekusi (aplikasi dapat terus berjalan dalam kasus ini)

Skenario ini cukup langka, tetapi hanya untuk menunjukkan bahwa jawabannya tidak SELALU "Ya", itu sebagian besar waktu "Ya" dan kadang-kadang, dalam kondisi langka, "Tidak".

Jonathan Perry
sumber
0

Tujuan utama dari blok akhirnya adalah untuk mengeksekusi apa pun yang tertulis di dalamnya. Seharusnya tidak tergantung pada apa pun yang terjadi dalam mencoba atau menangkap. Namun dengan System.Environment.Exit (1) aplikasi akan keluar tanpa pindah ke baris kode berikutnya.

Ashish Sahu
sumber