Что произойдет, если блок finally генерирует исключение?

266

Если блок finally создает исключение, что именно происходит?

В частности, что происходит, если исключение выдается на полпути через блок finally. Вызваны ли остальные операторы (после) в этом блоке?

Я знаю, что исключения будут распространяться вверх.

Джек Када
источник
8
Почему бы просто не попробовать? Но в таких вещах мне больше всего нравится возвращаться до finally, а затем возвращать что-то еще из блока finally. :)
ANEves
8
Все операторы в блоке finally должны выполняться. У этого не может быть возврата. msdn.microsoft.com/en-us/library/0hbbzekw(VS.80).aspx
Тим Скарборо

Ответы:

419

Если блок finally создает исключение, что именно происходит?

Это исключение распространяется вверх и вверх и будет (может) обрабатываться на более высоком уровне.

Ваш блок finally не будет завершен после того, как будет сгенерировано исключение.

Если блок finally выполнялся во время обработки более раннего исключения, то это первое исключение теряется.

Спецификация языка C # 4 § 8.9.5: Если блок finally вызывает другое исключение, обработка текущего исключения прекращается.

Хенк Холтерман
источник
9
Если это не так ThreadAbortException, тогда весь блок finally будет закончен первым, так как это критический раздел.
Дмитрий Шевченко
1
@Shedal - вы правы, но это относится только к «определенным асинхронным исключениям», т.е. ThreadAbortException. Для нормального однопоточного кода мой ответ верен.
Хенк Холтерман
«Первое исключение потеряно» - это на самом деле очень разочаровывает, случайно я нахожу объекты IDisposable, которые выдают исключение в Dispose (), что приводит к потере исключения внутри предложения «using».
Алексей Бурцев
«Я нахожу IDisposable объекты, которые выдают исключение в Dispose ()» - это странно, если не сказать больше. Читайте в MSDN: ИЗБЕГАЙТЕ выбрасывать исключение из Dispose (bool), кроме как под ...
Хенк Холтерман
1
@HenkHolterman: Ошибки переполнения диска не очень распространены на первичном жестком диске, подключенном напрямую, но программы иногда записывают файлы на съемные или сетевые диски; проблемы могут быть гораздо более распространенными с теми. Если кто-то выдернет USB-флешку до того, как файл будет полностью записан, было бы лучше немедленно сообщить об этом, чем ждать, пока они доберутся до своего места и обнаружат, что файл поврежден. Уступка более ранней ошибке при ее наличии может быть разумным поведением, но при отсутствии более ранней ошибки было бы лучше сообщить о проблеме, чем оставить ее незамеченной.
Суперкат
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выполняются блоки. Обратите внимание, что код в блоке finally после создания исключения не будет выполнен (фактически, в этом примере программы Visual Studio даже предупредит вас, что обнаружил недоступный код):

Исключение обработки внутреннего блока catch, выданное из блока try.
Внутренний, наконец, блок
Исключение обработки внешнего блока catch, выданное из блока finally.
Наружный наконец-то блок

Дополнительное замечание

Как отметил Михаил Даматов, исключение из 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");
        }
    }
}

Как видно из вывода, внутреннее исключение «потеряно» (т.е. проигнорировано):

Внутренний, наконец, блок
Исключение обработки внешнего блока catch, выданное из блока finally.
Наружный наконец-то блок
Дирк Воллмар
источник
2
Поскольку вы отбрасываете исключение в своем внутреннем улове, «Внутренний окончательный блок» никогда не будет достигнут в этом примере
Theofanis Pantelides
4
@Theofanis Pantelides: Нет, finallyблок будет (почти) всегда выполняться, это также относится и к внутреннему блоку finally (просто попробуйте пример программы самостоятельно (блок finally не будет выполнен в случае невосстановимого). исключение, например, an EngineExecutionException, но в таком случае ваша программа будет немедленно прервана в любом случае).
Дирк Воллмар
1
Я не понимаю, какова роль броска в первом уловке вашего первого куска кода. Я пробовал с и без него с консольным приложением, никакой разницы не нашел.
JohnPan
@johnpan: цель состояла в том, чтобы показать, что блок finally всегда выполняется, даже если оба блока try и catch выдают исключение. Там действительно нет разницы в выводе консоли.
Дирк Воллмар
10

Если есть ожидающее исключение (когда tryблок имеет, finallyно нет catch), новое исключение заменяет это.

Если нет ожидающих исключений, оно работает так же, как и исключение вне finallyблока.

Guffa
источник
Исключение может также находиться на рассмотрении , если это согласующий catchблок , который (перо) вызывает исключение.
stakx - больше не участвует
4

Исключение распространяется.

Дарин димитров
источник
2
@bitbonk: изнутри, как обычно.
Писквор покинул здание
3

Быстрый (и довольно очевидный) фрагмент для сохранения «оригинального исключения» (брошенного в tryблоке) и принесения в жертву «окончательного исключения» (брошенного в finallyблоке), в случае, если для вас важнее оригинальное:

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

Когда приведенный выше код выполняется, «Исходное исключение» распространяется вверх по стеку вызовов, а «Окончательное исключение» теряется.

LXA
источник
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();
    }
}

если webRequest был создан, но во время

using (var sw = webRequest.GetRequestStream())

тогда, наконец, ловит исключение, пытаясь закрыть соединения, которые, по его мнению, были открыты, потому что был создан веб-запрос.

Если, наконец, не было try-catch внутри, этот код вызовет необработанное исключение при очистке 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);
        }
    }
  • Запустите код, и вы увидите «второе исключение»
  • Раскомментируйте операторы try и catch, и вы увидите «первое исключение»
  • Также раскомментируйте бросок; заявление, и вы увидите «второе исключение» снова.
Дуг Коберн
источник
Стоит отметить, что для очистки «серьезного» исключения, которое может быть перехвачено только за пределами конкретного блока кода, можно генерировать исключение, которое перехватывается и обрабатывается внутри него. Используя фильтры исключений (доступные в vb.net, но не в C #), можно обнаружить это условие. Не так много кода можно сделать, чтобы «справиться» с ним, хотя, если кто-то использует какую-либо инфраструктуру журналирования, почти наверняка стоит регистрироваться. Подход C ++ к наличию исключений, возникающих в ходе очистки, вызывает системный сбой, но уродлив, но исключение исчезновения является ИМХО отвратительным.
суперкат
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

Исключение распространяется вверх и должно обрабатываться на более высоком уровне. Если исключение не обрабатывается на более высоком уровне, приложение вылетает. Выполнение блока finally останавливается в точке, где выдается исключение.

Независимо от того, есть ли исключение или нет, блок "finally" гарантированно будет выполнен.

  1. Если блок finally выполняется после возникновения исключения в блоке try,

  2. и если это исключение не обрабатывается

  3. и если блок finally создает исключение

Тогда исходное исключение, которое произошло в блоке try, теряется.

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

Выдает исключение;) Вы можете перехватить это исключение в другом предложении catch.

JHollanti
источник