O que acontece se um bloco finalmente lança uma exceção?

266

Se um bloco finalmente lança uma exceção, o que exatamente acontece?

Especificamente, o que acontece se a exceção for lançada no meio de um bloco finalmente. O restante das instruções (depois) deste bloco é invocado?

Estou ciente de que as exceções se propagarão para cima.

Jack Kada
fonte
8
Por que não apenas tentar? Mas nesse tipo de coisa, a que eu mais gosto é o retorno antes do finalmente e depois algo mais do bloco finalmente. :)
ANeves
8
Todas as instruções em um bloco finalmente devem ser executadas. Não pode ter um retorno. msdn.microsoft.com/pt-br/library/0hbbzekw(VS.80).aspx
Tim Scarborough

Respostas:

419

Se um bloco finalmente lança uma exceção, o que exatamente acontece?

Essa exceção se propaga para fora e para cima e será (pode) tratada em um nível superior.

Seu bloqueio final não será concluído além do ponto em que a exceção é lançada.

Se o bloco finalmente estiver sendo executado durante o tratamento de uma exceção anterior, essa primeira exceção será perdida.

Especificação da linguagem C # 4 § 8.9.5: Se o bloco final lançar outra exceção, o processamento da exceção atual será encerrado.

Henk Holterman
fonte
9
A menos que seja um ThreadAbortException, então todo o bloco final será finalizado primeiro, pois é uma seção crítica.
Dmytro Shevchenko
1
@Shedal - você está certo, mas isso se aplica apenas a "certas exceções assíncronas", ou seja, ThreadAbortException. Para código normal de 1 thread, minha resposta é válida.
Henk Holterman
"Primeira exceção é perdida" - isso é realmente muito decepcionante, por acaso acho objetos IDisposable que lançam exceção em Dispose (), que resultam na exceção perdida na cláusula "using".
Alex Burtsev
"Acho objetos IDisposable que lançam exceção em Dispose ()" - isso é estranho, para dizer o mínimo. Leia no MSDN: EVITAR lançar uma exceção dentro de Dispose (bool), exceto sob ...
Henk Holterman
1
@HenkHolterman: Erros de disco cheio não são muito comuns em um disco rígido primário diretamente conectado, mas os programas às vezes gravam arquivos em discos removíveis ou em rede; problemas podem ser muito mais comuns com eles. Se alguém puxar um pendrive antes que um arquivo seja totalmente gravado, seria melhor avisar imediatamente do que esperar até chegarem aonde estão indo e descobrir que o arquivo está corrompido. Ceder ao erro anterior quando existe um pode ser um comportamento sensato, mas quando não houver um erro anterior, seria melhor relatar o problema do que deixá-lo não relatado.
supercat
101

Para perguntas como essas, geralmente abro um projeto de aplicativo de console vazio no Visual Studio e escrevo um pequeno programa de exemplo:

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

Quando você executa o programa, verá a ordem exata na qual os blocos catche finallysão executados. Observe que o código no bloco final após o lançamento da exceção não será executado (de fato, neste programa de exemplo, o Visual Studio até avisará que ele detectou código inacessível):

Exceção de manipulação do bloco de captura interno lançada do bloco try.
Bloco finalmente interno
Exceção de manipulação do bloco de captura externa lançada do bloco finalmente.
Bloco externo finalmente

Observação adicional

Como Michael Damatov apontou, uma exceção do trybloco será "comida" se você não lidar com isso em um catchbloco (interno) . De fato, no exemplo acima, a exceção lançada novamente não aparece no bloco de captura externo. Para tornar isso ainda mais claro, observe a seguinte amostra ligeiramente modificada:

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

Como você pode ver na saída, a exceção interna é "perdida" (ou seja, ignorada):

Bloco finalmente interno
Exceção de manipulação do bloco de captura externa lançada do bloco finalmente.
Bloco externo finalmente
Dirk Vollmar
fonte
2
Como você lança a exceção em sua captura interna, 'Inner finalmente block' nunca será alcançado neste exemplo
Theofanis Pantelides
4
@Theofanis Pantelides: Não, um finallybloco (quase) sempre será executado; isso também vale para o bloco final interno (tente o programa de amostra você mesmo (um bloco finalmente não será executado no caso de um não recuperável) exceção, por exemplo, um EngineExecutionException, mas nesse caso a seu programa terminará imediatamente de qualquer maneira).
Dirk Vollmar
1
Porém, não vejo qual é o papel do arremesso na primeira captura do seu primeiro pedaço de código. Eu tentei com e sem ele com um aplicativo de console, nenhuma diferença encontrada.
JohnPan
@johnpan: O objetivo era mostrar que o bloco final sempre é executado, mesmo que o bloco try e catch lance uma exceção. De fato, não há diferença na saída do console.
Dirk Vollmar
10

Se houver uma exceção pendente (quando o try bloco tiver um finallymas não catch), a nova exceção substituirá essa.

Se não houver exceção pendente, ele funciona da mesma maneira que lança uma exceção fora do finallybloco.

Guffa
fonte
Uma exceção também pode estar pendente se não é uma correspondência catchbloco que (re) lança uma exceção.
stakx - não contribui mais com
4

A exceção é propagada.

Darin Dimitrov
fonte
2
@bitbonk: de dentro para fora, como de costume.
Piskvor saiu do prédio
3

Snippet rápido (e bastante óbvio) para salvar a "exceção original" (lançada em trybloco) e sacrificar a "finalmente exceção" (lançada em finallybloco), caso o original seja mais importante para você:

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

Quando o código acima é executado, "Exceção original" propaga a pilha de chamadas e "Finalmente exceção" é perdida.

lxa
fonte
2

Eu tive que fazer isso para detectar um erro ao tentar fechar um fluxo que nunca foi aberto devido a uma exceção.

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

se o webRequest foi criado, mas ocorreu um erro de conexão durante o

using (var sw = webRequest.GetRequestStream())

o finalmente capturaria uma exceção ao tentar fechar as conexões que julgava abertas porque o webRequest havia sido criado.

Se o finalmente não tivesse um try-catch dentro, esse código causaria uma exceção não tratada durante a limpeza do webRequest

if (webRequest.GetRequestStream() != null) 

a partir daí, o código sairia sem manipular adequadamente o erro que ocorreu e, portanto, causar problemas no método de chamada.

Espero que isso ajude como um exemplo

Emma Grant
fonte
1

Lançar uma exceção enquanto outra exceção estiver ativa resultará na substituição da primeira exceção pela segunda exceção (posterior).

Aqui está um código que ilustra o que acontece:

    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);
        }
    }
  • Execute o código e você verá "segunda exceção"
  • Remova o comentário das instruções try e catch e você verá a "primeira exceção"
  • Descomente também o arremesso; e você verá a "segunda exceção" novamente.
Doug Coburn
fonte
Vale a pena notar que é possível a limpeza de uma exceção "grave", que só seria capturada fora de um bloco de código específico, para lançar uma exceção que é capturada e manipulada dentro dele. Usando filtros de exceção (disponível em vb.net, embora não em C #), é possível detectar essa condição. Não há muito o que o código possa fazer para "manipulá-lo", embora se alguém estiver usando algum tipo de estrutura de registro, certamente vale a pena fazer o registro. A abordagem C ++ de ter exceções que ocorrem na limpeza acionam um colapso do sistema é feia, mas a exclusão de exceções é horrível para o IMHO.
Supercat 30/11
1

Há alguns meses, eu também enfrentei algo assim,

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

Para resolver esse problema, criei uma classe de utilidade como

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

E usado assim

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

mas se você quiser usar paramaters e retornar tipos, é outra história

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

A maneira como as exceções lançadas pelo CodeA e CodeB são tratadas é a mesma.

Uma exceção lançada em um finallybloco não tem nada de especial; trate-a como a exceção lançada pelo código B.

Cheng Chen
fonte
Você poderia elaborar? O que você quer dizer com as exceções são iguais?
Dirk Vollmar
1

A exceção se propaga e deve ser tratada em um nível superior. Se a exceção não for tratada no nível superior, o aplicativo trava. A execução do bloco "finalmente" para no ponto em que a exceção é lançada.

Independentemente de haver uma exceção ou não, o bloco "finalmente" é garantido para execução.

  1. Se o bloco "final" estiver sendo executado após uma exceção no bloco try,

  2. e se essa exceção não for tratada

  3. e se o bloco finalmente lança uma exceção

Em seguida, a exceção original que ocorreu no bloco try é perdida.

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

Ótimo artigo para detalhes

Raj Baral
fonte
-1

Ele lança uma exceção;) Você pode capturar essa exceção em alguma outra cláusula catch.

JHollanti
fonte