Що станеться, якщо нарешті блок кидає виняток?


266

Якщо нарешті блок кидає виняток, що саме відбувається?

Зокрема, що станеться, якщо виняток буде перекинуто на середину через остаточний блок. Чи викликаються решта тверджень (після) у цьому блоці?

Я знаю, що винятки поширюватимуться вгору.


8
Чому б просто не спробувати? Але в таких речах мені подобається найбільше повернення до останнього, а потім повернення ще чогось із остаточного блоку. :)
ANeves

8
Усі заяви в остаточному блоці повинні виконуватись. Він не може мати повернення. msdn.microsoft.com/en-us/library/0hbbzekw(VS.80).aspx
Тім Скарборо

Відповіді:


419

Якщо нарешті блок кидає виняток, що саме відбувається?

Цей виняток поширюється і збільшується, і його можна (можна) обробляти на більш високому рівні.

Ваш остаточний блок не буде завершений поза межею, де викинуто виняток.

Якщо остаточний блок був виконаний під час обробки попереднього винятку, то перший виняток втрачається.

Спеціалізація мови C # 4 § 8.9.5: Якщо остаточний блок кидає інший виняток, обробка поточного винятку припиняється.


9
Якщо це не a ThreadAbortException, тоді весь блок нарешті буде закінчений спочатку, оскільки це критичний розділ.
Дмитро Шевченко

1
@Shedal - ти маєш рацію, але це стосується лише "певних асинхронних винятків", тобто ThreadAbortException. Для звичайного коду в 1 потік відповідає моя відповідь.
Хенк Холтерман

"Перший виняток втрачено" - це насправді дуже знешкоджує; час від часу я знаходжу об'єкти, що не використовуються, які викидають виключення в Dispose (), в результаті чого виняток втрачається всередині "за допомогою".
Алекс Бурцев

"Я знаходжу об'єкти, що не використовуються для ID, які викидають виключення в Dispose ()" - це, мабуть, дивно. Читайте в MSDN: Уникніть викидання виключення зсередини Dispose (bool) за винятком ...
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 навіть попереджатиме вас, що виявив недоступний код):

Виняток обробки внутрішнього блоку лову, викинутий із спробу блоку.
Внутрішня нарешті блокується
Виняток з керування зовнішнім уловом блоком, викинутий з остаточно блоку.
Зовнішній нарешті блокувати

Додаткове зауваження

Як зазначав Михайло Даматов, виняток із tryблоку буде "з'їдений", якщо ви не обробляєте його у внутрішньому 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");
        }
    }
}

Як видно з результату, внутрішній виняток "втрачається" (тобто ігнорується):

Внутрішня нарешті блокується
Виняток з керування зовнішнім уловом блоком, викинутий з остаточно блоку.
Зовнішній нарешті блокувати

2
Оскільки ви ВІДКРИТИ Виняток у своєму внутрішньому улові, "Внутрішній нарешті блокувати" ніколи не буде досягнуто в цьому прикладі
Теофаніс Пантелідес

4
@Theofanis Pantelides: Ні, finallyблок (майже) завжди буде виконуватися, це також стосується цього випадку для внутрішнього остаточного блоку (просто спробуйте зразкову програму самостійно (Зрештою, блок не буде виконаний у випадку неповернення виняток, наприклад EngineExecutionException, але в такому випадку ваша програма все одно припиниться)
Дірк Волмар

1
Я не бачу, яка ж роль для кидка у першому уловленні вашого першого коду. Я намагався з консольним додатком і без нього, різниці не знайдено.
ДжонПан

@johnpan: Сенс полягав у тому, щоб показати, що остаточний блок завжди виконується, навіть якщо обидва намагаються і зловити блок кидають виняток. Дійсно немає різниці у виході консолі.
Дірк Волмар

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

якщо веб-запит був створений, але помилка підключення сталася під час

using (var sw = webRequest.GetRequestStream())

то, нарешті, буде спіймано виняток, який намагається закрити зв’язки, які, на його думку, були відкритими, оскільки створено веб-запит.

Якщо нарешті не було спроби вловлювати всередині, цей код може спричинити необроблений виняток під час очищення 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);
        }
    }
  • Запустіть код, і ви побачите "другий виняток"
  • Відмініть коментарі про випробування та ловлі, і ви побачите "перший виняток"
  • Також коментуйте кидок; і ви знову побачите "другий виняток".

Варто зазначити, що при очищенні "суворого" винятку, який би потрапляв лише за межі певного кодового блоку, можливо викинути виняток, який потрапляє та обробляється всередині нього. Використовуючи фільтри виключень (доступні на vb.net, хоча не C #), можна виявити цю умову. Існує не так багато, що код може зробити, щоб "обробити" його, хоча якщо використовується будь-який фреймворк ведення журналу, він майже напевно вартує реєстрації. Підхід C ++, який має винятки, які виникають під час очищення, викликає крах системи, але зникнення винятків - огидний для IMHO.
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.


Не могли б ви детальніше? Що ви маєте на увазі, що винятки однакові?
Дірк Волмар

1

Виняток поширюється вгору, і його слід обробляти на більш високому рівні. Якщо виняток не обробляється на більш високому рівні, програма виходить з ладу. Виконання блоку "нарешті" зупиняється на тому місці, де викидається виняток.

Незалежно від того, є виняток чи ні, "нарешті" блок гарантовано виконується.

  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

Це виняток;) Ви можете вилучити цей виняток у будь-якому іншому пункті вилучення.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.