O código em uma instrução Finalmente será acionado se eu retornar um valor em um bloco Try?

237

Estou revendo algum código para um amigo e digo que ele estava usando uma declaração de retorno dentro de um bloco try-finally. O código na seção Finalmente ainda é acionado, embora o restante do bloco try não seja?

Exemplo:

public bool someMethod()
{
  try
  {
    return true;
    throw new Exception("test"); // doesn't seem to get executed
  }
  finally
  {
    //code in question
  }
}
JamesEggers
fonte
1
stackoverflow.com/q/449099
amit jha 31/03
Ser tratado aqui significa: apanhado. Mesmo um bloco de captura vazio em seu manipulador global é suficiente. Além disso, existem exceções que não podem ser manipulados: StackOverflowException, ExecutionEngineExceptionsão algumas delas. E como eles não podem ser manipulados, o finallynão funcionará.
Abel
8
@Abel: Você parece estar falando sobre uma situação diferente. Esta pergunta é sobre retornar em um trybloco. Nada sobre abortos abruptos de programas.
22816 Jon Skeet
6
@Abel: Não sei ao certo o que você quer dizer com "retornar após uma exceção", mas isso não parece ser o que está sendo perguntado aqui. Veja o código - a primeira instrução do trybloco é uma returninstrução. (A segunda indicação de que o bloqueio é inacessível e vai gerar um aviso.)
Jon Skeet
4
@ Abel: De fato, e se a pergunta tivesse sido "O código em uma declaração finalmente será sempre executado em todas as situações" que teria sido relevante. Mas não era isso que estava sendo perguntado.
21816 Jon Skeet

Respostas:

265

Resposta simples: Sim.

Andrew Rollings
fonte
9
@Edi: Hum, não vejo o que os tópicos de plano de fundo têm a ver com isso. Basicamente, a menos que todo o processo seja interrompido, o finallybloco será executado.
22816 Jon Skeet
3
A resposta longa é que você não necessariamente terá um bloco para executar se algo catastrófico acontecer, como uma pilha excedente, uma exceção de falta de memória, uma falha grave de algum tipo ou se alguém desconectar sua máquina na hora certa. . Mas para todos os efeitos, a menos que você esteja fazendo algo muito, muito errado, o bloqueio final sempre será acionado.
Andrew Rollings
Se o código antes da tentativa falhar devido a algum motivo, notei que finalmente ainda é executado. Nesse caso, você pode adicionar uma condição para, finalmente, que satisfaz os critérios a serem executados, finalmente, apenas quando o código sob for executado com êxito
kjosh 29/04
200

Normalmente sim. A seção finalmente é garantida para executar o que acontecer, incluindo exceções ou declaração de retorno. Uma exceção a esta regra é uma exceção assíncrona que ocorre no encadeamento ( OutOfMemoryException, StackOverflowException).

Para saber mais sobre exceções assíncronas e código confiável nessas situações, leia sobre regiões de execução restritas .

Mehrdad Afshari
fonte
154

Aqui está um pequeno teste:

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

O resultado é:

before
finally
return
after
Jon B
fonte
10
@ CodeBlend: Tem a ver com quando WriteLineo resultado da chamada do método é realmente realizado. Nesse caso, a returnchamada define o resultado dos métodos, finalmente grava no console - antes que o método saia. Em seguida, o WriteLinemétodo in principal cospe o texto da chamada de retorno.
NotMe
38

Citando no MSDN

Finalmente, é usado para garantir que um bloco de código de instrução seja executado, independentemente de como o bloco try anterior foi encerrado .

Perpetualcoder
fonte
3
Bem, além dos casos excepcionais de Mehrdad, finalmente também não será chamado se você estiver depurando em um bloco try e pare a depuração :). Parece que nada na vida é garantido.
StuartLC 22/08/12
1
Não é bem assim, citando no MSDN : No entanto, se a exceção não for tratada, a execução do bloco final dependerá de como a operação de desenrolamento da exceção é acionada. Por sua vez, isso depende de como o seu computador está configurado. .
Abel
1
@Abel faz um ponto crucial aqui. Todos podem ver como um bloco final não é executado se, por exemplo, o programa for abortado via gerenciador de tarefas. Mas esta resposta é, como está, claramente errada: finallyna verdade, não garante nada, mesmo para programas perfeitamente comuns (que por acaso lançaram essa exceção).
Peter - Restabelece Monica
19

Geralmente sim, o finalmente será executado.

Para os três cenários a seguir, o finalmente será sempre executado:

  1. Nenhuma exceção ocorre
  2. Exceções síncronas (exceções que ocorrem no fluxo normal do programa).
    Isso inclui exceções compatíveis com CLS derivadas de System.Exception e exceções não compatíveis com CLS, que não derivam de System.Exception. Exceções não compatíveis com CLS são agrupadas automaticamente pelo RuntimeWrappedException. O C # não pode gerar exceções de reclamação que não sejam do CLS, mas idiomas como o C ++ podem. O C # pode estar chamando o código escrito em um idioma que pode lançar exceções não compatíveis com CLS.
  3. ThreadAbortException assíncrono
    A partir do .NET 2.0, um ThreadAbortException não impedirá mais que um finalmente seja executado. ThreadAbortException agora é içado para antes ou depois do finalmente. O finalmente sempre será executado e não será interrompido por uma interrupção de encadeamento, desde que a tentativa tenha sido realmente inserida antes da ocorrência do cancelamento de encadeamento.

O cenário a seguir, o finalmente não será executado:

StackOverflowException assíncrono.
A partir do .NET 2.0, um estouro de pilha fará com que o processo seja encerrado. O finalmente não será executado, a menos que uma restrição adicional seja aplicada para tornar o finalmente um CER (Região de Execução Restrita). As RCEs não devem ser usadas no código geral do usuário. Eles devem ser usados ​​apenas onde for crítico que o código de limpeza sempre seja executado - depois que todo o processo for encerrado pelo excesso de pilha de qualquer maneira e, portanto, todos os objetos gerenciados serão limpos por padrão. Portanto, o único local em que um CER deve ser relevante é para recursos alocados fora do processo, por exemplo, identificadores não gerenciados.

Normalmente, o código não gerenciado é agrupado por alguma classe gerenciada antes de ser consumido pelo código do usuário. A classe de wrapper gerenciado normalmente usa um SafeHandle para quebrar o identificador não gerenciado. O SafeHandle implementa um finalizador crítico e um método Release que é executado em um CER para garantir a execução do código de limpeza. Por esse motivo, você não deve ver as CERs espalhadas pelo código do usuário.

Portanto, o fato de finalmente não executar no StackOverflowException não deve afetar o código do usuário, pois o processo será encerrado de qualquer maneira. Se você tem algum caso extremo em que precisa limpar algum recurso não gerenciado, fora de um SafeHandle ou CriticalFinalizerObject, use um CER da seguinte maneira; mas observe que isso é uma prática ruim - o conceito não gerenciado deve ser abstraído para uma classe gerenciada e SafeHandle (s) apropriado (s) por design.

por exemplo,

// 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
fonte
Observe que mesmo um CER não será executado no caso de uma SOE. No momento em que você escreveu isso, a documentação do MSDN no CER estava errada / incompleta. Um SOE acionará um FailFastinternamente. A única maneira de conseguir capturá-los é personalizando a hospedagem de tempo de execução do CLR . Observe que seu ponto ainda é válido para outras exceções assíncronas.
Abel
10

Há uma exceção muito importante a isso, que não vi mencionada em nenhuma outra resposta, e que (depois de programar em C # há 18 anos) não acredito que não sabia.

Se você lançar ou disparar uma exceção de qualquer tipo dentro do seu catchbloco (não apenas esquisito StackOverflowExceptionse coisas desse tipo), e você não tiver o try/catch/finallybloco inteiro dentro de outro try/catchbloco, seu finallybloco não será executado. Isso é facilmente demonstrado - e se eu não o tivesse visto, dada a frequência com que li que são apenas pequenas caixas de esquina realmente estranhas que podem fazer com que um finallybloco não seja executado, eu não teria acreditado.

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

Tenho certeza de que há uma razão para isso, mas é bizarro que não seja mais conhecido. (Ele está anotado aqui, por exemplo, mas não em nenhum lugar desta pergunta em particular.)

Ken Smith
fonte
Bem, o finallybloco simplesmente não é um problema para o catchbloco.
Peter - Restabelece Monica
7

Sei que estou atrasado para a festa, mas no cenário (diferente do exemplo do OP) em que de fato uma exceção é lançada, os estados do MSDN ( https://msdn.microsoft.com/en-us/library/zwc8s4fz.aspx ): "Se a exceção não for capturada, a execução do bloco final depende se o sistema operacional escolhe acionar uma operação de desenrolamento de exceção."

O bloco final é garantido apenas para execução se alguma outra função (como Principal) mais adiante na pilha de chamadas capturar a exceção. Esse detalhe geralmente não é um problema porque todos os programas C # de ambientes de tempo de execução (CLR e OS) são executados gratuitamente na maioria dos recursos que um processo possui quando sai (manipula arquivos etc.). Em alguns casos, pode ser crucial: Uma operação de banco de dados parcialmente em andamento que você deseja confirmar. descontrair; ou alguma conexão remota que talvez não seja fechada automaticamente pelo SO e bloqueie um servidor.

Peter - Restabelecer Monica
fonte
3

Sim. Esse é, de fato, o ponto principal de uma declaração finalmente. A menos que algo catastrófico ocorra (falta de memória, computador desconectado etc.), a instrução finalmente deve sempre ser executada.

Jeffrey L Whitledge
fonte
1
Eu imploro para discordar. Cf. minha resposta. Uma exceção perfeitamente normal no bloco try é suficiente para ignorar o finallybloco se essa exceção nunca for capturada. Isso me pegou de surpresa ;-).
Peter - Restabelece Monica
@ PeterA.Schneider - Isso é interessante. Obviamente, as implicações disso realmente não mudam muito. Se nada na pilha de chamadas capturar a exceção, isso deve significar (se bem entendi) que o processo está prestes a ser encerrado. Portanto, é semelhante à situação do tipo "puxe o plug". Eu acho que o principal argumento é que você deve sempre ter uma captura de exceção de nível superior ou sem tratamento.
Jeffrey L Whitledge
Exatamente, é isso que eu entendo também. Achei isso surpreendente também. Verdadeiro: os recursos mais comuns serão liberados automaticamente, em particular a memória e os arquivos abertos. Porém, os recursos que o sistema operacional não conhece - conexões abertas de servidor ou banco de dados como exemplo - podem permanecer abertas no lado remoto, porque nunca foram desligadas adequadamente; soquetes permanecem, etc. Acho que é prudente ter um manipulador de exceção de nível superior, sim.
Peter - Restabelece Monica
2

finalmente não será executado caso você esteja saindo do aplicativo usando System.exit (0); como em

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

o resultado seria apenas: tente

Hakuna Matata
fonte
4
Você está respondendo no idioma errado, suponho, a pergunta era sobre c#, mas isso parece ser Java. E, além disso, na maioria dos casos System.exit()é um indício de uma má concepção :-)
z00l
0

Em 99% dos cenários, será garantido que o código dentro do finallybloco será executado, no entanto, pense neste cenário: Você possui um encadeamento que possui um try-> finallyblock (no catch) e recebe uma exceção não tratada dentro desse encadeamento. Nesse caso, o encadeamento sairá e seufinally bloco não será executado (o aplicativo pode continuar sendo executado nesse caso)

Esse cenário é bastante raro, mas é apenas para mostrar que a resposta não é SEMPRE "Sim", na maioria das vezes é "Sim" e, às vezes, em raras condições, "Não".

Jonathan Perry
fonte
0

O principal objetivo do bloco finalmente é executar o que estiver escrito dentro dele. No entanto, com o System.Environment.Exit (1), o aplicativo será encerrado sem passar para a próxima linha de código.

Ashish Sahu
fonte