Apa yang terjadi jika blok akhirnya melempar pengecualian?

266

Jika blok akhirnya melempar pengecualian, apa yang sebenarnya terjadi?

Secara khusus, apa yang terjadi jika pengecualian dilemparkan di tengah jalan melalui blok akhirnya. Apakah sisa pernyataan (setelah) di blok ini dipanggil?

Saya menyadari bahwa pengecualian akan menyebar ke atas.

Jack Kada
sumber
8
Kenapa tidak mencobanya saja? Tetapi pada hal-hal semacam ini, yang paling saya sukai adalah kembali sebelum akhirnya dan kemudian mengembalikan sesuatu yang lain dari blok akhirnya. :)
ANeves
8
Semua pernyataan di blok akhirnya harus dijalankan. Tidak bisa kembali. msdn.microsoft.com/en-us/library/0hbbzekw(VS.80).aspx
Tim Scarborough

Jawaban:

419

Jika blok akhirnya melempar pengecualian apa yang sebenarnya terjadi?

Pengecualian itu menyebar keluar dan naik, dan akan (dapat) ditangani pada tingkat yang lebih tinggi.

Blok Anda akhirnya tidak akan selesai melampaui titik di mana pengecualian dilemparkan.

Jika blok akhirnya dieksekusi selama penanganan pengecualian sebelumnya maka pengecualian pertama hilang.

C # 4 Spesifikasi Bahasa § 8.9.5: Jika blok akhirnya mengeluarkan pengecualian lain, pemrosesan pengecualian saat ini dihentikan.

Henk Holterman
sumber
9
Kecuali itu a ThreadAbortException, maka seluruh blok akhirnya akan selesai terlebih dahulu, karena merupakan bagian kritis.
Dmytro Shevchenko
1
@ Shedal - Anda benar tetapi itu hanya berlaku untuk "pengecualian asinkron tertentu", yaitu ThreadAbortException. Untuk kode 1-thread normal, jawaban saya berlaku.
Henk Holterman
"Pengecualian pertama hilang" - itu sebenarnya sangat mengecewakan, secara tidak sengaja saya menemukan objek IDisposable yang melempar pengecualian dalam Buang (), yang menghasilkan pengecualian yang hilang di dalam "menggunakan" klausa.
Alex Burtsev
"Saya menemukan objek IDisposable yang melempar pengecualian di Buang ()" - itu aneh untuk sedikitnya. Baca di MSDN: HINDARI lempar pengecualian dari dalam Buang (bool) kecuali di bawah ...
Henk Holterman
1
@HenkHolterman: Kesalahan disk-penuh tidak terlalu umum pada hard disk primer yang terhubung langsung, tetapi program terkadang menulis file ke disk yang dapat dilepas atau jaringan; masalah bisa jauh lebih umum dengan masalah itu. Jika seseorang mencabut stik USB sebelum file telah ditulis sepenuhnya, akan lebih baik untuk memberi tahu mereka segera daripada menunggu sampai mereka tiba di mana mereka pergi dan menemukan file tersebut rusak. Menghasilkan kesalahan sebelumnya ketika ada satu perilaku yang masuk akal, tetapi ketika tidak ada kesalahan sebelumnya akan lebih baik untuk melaporkan masalah daripada membiarkannya tidak dilaporkan.
supercat
101

Untuk pertanyaan seperti ini, saya biasanya membuka proyek aplikasi konsol kosong di Visual Studio dan menulis program sampel kecil:

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

Ketika Anda menjalankan program ini, Anda akan melihat urutan yang tepat di mana catchdan finallyblok dieksekusi. Harap dicatat bahwa kode di blok akhirnya setelah pengecualian dilemparkan tidak akan dieksekusi (pada kenyataannya, dalam program sampel ini Visual Studio bahkan akan memperingatkan Anda bahwa ia telah mendeteksi kode yang tidak terjangkau):

Pengecualian penanganan blok tangkapan dalam dilemparkan dari blok percobaan.
Bagian dalam akhirnya menghalangi
Pengecualian penanganan tangkapan blok luar dilempar dari akhirnya blok.
Bagian luar akhirnya menghalangi

Keterangan tambahan

Seperti yang ditunjukkan Michael Damatov, pengecualian dari tryblok akan "dimakan" jika Anda tidak menanganinya di catchblok (bagian dalam) . Bahkan, dalam contoh di atas pengecualian yang dilemparkan kembali tidak muncul di blok tangkapan luar. Untuk membuatnya terlihat lebih jelas pada sampel yang sedikit dimodifikasi berikut ini:

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

Seperti yang Anda lihat dari output, pengecualian dalam adalah "hilang" (yaitu diabaikan):

Bagian dalam akhirnya menghalangi
Pengecualian penanganan tangkapan blok luar dilempar dari akhirnya blok.
Bagian luar akhirnya menghalangi
Dirk Vollmar
sumber
2
Karena Anda MENDAPATKAN Pengecualian dalam tangkapan batin Anda, 'Bagian dalam akhirnya menghalangi' tidak akan pernah tercapai dalam contoh ini
Theofanis Pantelides
4
@Theofanis Pantelides: Tidak, finallyblok akan (hampir) selalu dieksekusi, ini berlaku juga dalam hal ini untuk blok akhirnya dalam (coba saja program sampel sendiri (Blok akhirnya tidak akan dieksekusi dalam kasus yang tidak dapat dipulihkan) pengecualian, misalnya EngineExecutionException, tetapi dalam kasus seperti itu program Anda akan segera dihentikan)
Dirk Vollmar
1
Saya tidak melihat apa peran lemparan ke tangkapan pertama dari potongan kode pertama Anda. Saya mencoba dengan dan tanpa itu dengan aplikasi konsol, tidak ada perbedaan yang ditemukan.
JohnPan
@ Johnpan: Intinya adalah untuk menunjukkan bahwa blok akhirnya selalu dijalankan, bahkan jika keduanya mencoba dan menangkap blok melempar pengecualian. Memang tidak ada perbedaan dalam output konsol.
Dirk Vollmar
10

Jika ada pengecualian tertunda (ketika tryblok memiliki finallytetapi tidak catch), pengecualian baru menggantikan yang itu.

Jika tidak ada pengecualian yang tertunda, ia berfungsi seperti melempar pengecualian di luar finallyblok.

Guffa
sumber
Pengecualian mungkin juga akan tertunda jika ada adalah pencocokan catchblok yang (ulang) melempar pengecualian.
stakx - tidak lagi berkontribusi
4

Pengecualian disebarkan.

Darin Dimitrov
sumber
2
@bitbonk: dari dalam ke luar, seperti biasa.
Piskvor meninggalkan gedung
3

Cuplikan cepat (dan agak jelas) untuk menyimpan "pengecualian asli" (dilemparkan ke dalam tryblok) dan mengorbankan "akhirnya pengecualian" (dilemparkan ke dalam finallyblok), jika yang asli lebih penting bagi Anda:

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

Ketika kode di atas dijalankan, "Pengecualian Asli" menyebarkan tumpukan panggilan, dan "Pengecualian Akhirnya" hilang.

lxa
sumber
2

Saya harus melakukan ini untuk menangkap kesalahan saat mencoba menutup aliran yang tidak pernah dibuka karena pengecualian.

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

jika webRequest dibuat tetapi terjadi kesalahan koneksi selama

using (var sw = webRequest.GetRequestStream())

kemudian yang terakhir akan menangkap pengecualian yang mencoba menutup koneksi yang dianggapnya terbuka karena webRequest telah dibuat.

Jika akhirnya tidak memiliki try-catch di dalam, kode ini akan menyebabkan pengecualian tidak tertangani saat membersihkan webRequest

if (webRequest.GetRequestStream() != null) 

dari sana kode akan keluar tanpa benar menangani kesalahan yang terjadi dan karena itu menyebabkan masalah untuk metode panggilan.

Semoga ini bisa membantu sebagai contoh

Emma Grant
sumber
1

Melempar pengecualian saat pengecualian lain aktif akan menghasilkan pengecualian pertama digantikan oleh pengecualian kedua (nanti).

Berikut adalah beberapa kode yang menggambarkan apa yang terjadi:

    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);
        }
    }
  • Jalankan kode dan Anda akan melihat "pengecualian kedua"
  • Batalkan komentar mencoba dan menangkap pernyataan dan Anda akan melihat "pengecualian pertama"
  • Hapus tanda komentar lemparan; pernyataan dan Anda akan melihat "pengecualian kedua" lagi.
Doug Coburn
sumber
Perlu dicatat bahwa pembersihan pengecualian "parah" mungkin dilakukan hanya di luar blok kode tertentu untuk membuang pengecualian yang ditangkap dan ditangani di dalamnya. Menggunakan filter pengecualian (tersedia di vb.net, meskipun bukan C #) dimungkinkan untuk mendeteksi kondisi ini. Tidak ada banyak kode yang dapat dilakukan untuk "menanganinya", meskipun jika seseorang menggunakan kerangka logging apa pun, hampir pasti layak untuk dicatat. Pendekatan C ++ memiliki pengecualian yang terjadi di dalam pembersihan memicu krisis sistem jelek, tetapi memiliki pengecualian menghilang adalah IMHO mengerikan.
supercat
1

Beberapa bulan yang lalu saya juga menghadapi sesuatu seperti ini,

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

Untuk mengatasi masalah tersebut saya membuat kelas utilitas seperti

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

Dan digunakan seperti ini

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

tetapi jika Anda ingin menggunakan paramater dan mengembalikan tipe itu adalah cerita lain

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

Cara pengecualian yang dilontarkan oleh CodeA dan CodeB ditangani adalah sama.

Pengecualian yang dilemparkan ke dalam finallyblok tidak ada yang spesial, memperlakukannya sebagai pengecualian dengan membuang kode B.

Cheng Chen
sumber
Bisakah Anda menguraikan? Apa yang Anda maksud dengan pengecualian yang sama?
Dirk Vollmar
1

Pengecualian menyebar, dan harus ditangani di tingkat yang lebih tinggi. Jika pengecualian tidak ditangani di tingkat yang lebih tinggi, aplikasi macet. Eksekusi blok "akhirnya" berhenti pada titik di mana pengecualian dilemparkan.

Terlepas dari apakah ada pengecualian atau tidak, blok "akhirnya" dijamin untuk dieksekusi.

  1. Jika blok "akhirnya" dijalankan setelah pengecualian terjadi di blok coba,

  2. dan jika pengecualian itu tidak ditangani

  3. dan jika blok akhirnya melempar pengecualian

Maka pengecualian asli yang terjadi di blok percobaan hilang.

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

Artikel bagus untuk Detail

Raj Baral
sumber
-1

Ini melempar pengecualian;) Anda dapat menangkap pengecualian itu dalam beberapa klausa tangkapan lainnya.

JHollanti
sumber