C # captura uma exceção de estouro de pilha


115

Eu tenho uma chamada recursiva para um método que lança uma exceção de estouro de pilha. A primeira chamada é cercada por um bloco try catch, mas a exceção não é detectada.

A exceção de estouro de pilha se comporta de maneira especial? Posso capturar / manipular a exceção adequadamente?

Não tenho certeza se relevante, mas informações adicionais:

  • a exceção não é lançada no tópico principal

  • o objeto onde o código está lançando a exceção é carregado manualmente por Assembly.LoadFrom (...). CreateInstance (...)


3
@RichardOD, claro que consertei o bug porque era um bug. No entanto, o problema pode aparecer de uma maneira diferente e eu quero lidar com isso
Toto

7
Concordo, um estouro de pilha é um erro sério que não pode ser detectado porque não deveria ser detectado. Em vez disso, corrija o código quebrado.
Ian Kemp,

11
@RichardOD: Se alguém deseja projetar, por exemplo, um analisador descendente recursivo e não impor limites artificiais de profundidade além daqueles realmente exigidos pela máquina host, como fazer isso? Se eu tivesse meu druthers, haveria uma exceção StackCritical que poderia ser capturada explicitamente, que seria acionada enquanto ainda houvesse um pouco de espaço restante na pilha; ele se desabilitaria até que fosse realmente lançado e não poderia ser capturado até que uma quantidade segura de espaço de pilha permanecesse.
supercat

2
Esta pergunta é útil - eu quero falhar em um teste de unidade se ocorrer uma exceção de estouro de pilha - mas o NUnit apenas move o teste para a categoria "ignorado" em vez de falhar como faria com outras exceções - eu preciso pegá-lo e faça um em Assert.Failvez disso. Então, sério - como vamos fazer isso?
BrainSlugs83

Respostas:


109

A partir do 2.0, uma exceção StackOverflow só pode ser detectada nas seguintes circunstâncias.

  1. O CLR está sendo executado em um ambiente hospedado * onde o host permite especificamente que as exceções StackOverflow sejam tratadas
  2. A exceção stackoverflow é lançada pelo código do usuário e não devido a uma situação real de estouro de pilha ( Referência )

* "ambiente hospedado" como em "meu código hospeda CLR e eu configuro as opções de CLR" e não "meu código é executado em hospedagem compartilhada"


27
Se ele não pode ser capturado em nenhum cenário relevante, por que existe o objeto StackoverflowException?
Manu

9
@Manu por pelo menos alguns motivos. 1) É que poderia ser capturado, mais ou menos, em 1.1 e, portanto, tinha um propósito. 2) Ele ainda pode ser detectado se você estiver hospedando o CLR, então ainda é um tipo de exceção válido
JaredPar

3
Se não puder ser detectado ... Por que o evento do Windows que explica o que aconteceu não inclui o rastreamento de pilha completo por padrão?

10
Como alguém pode permitir que StackOverflowExceptions seja manipulado em um ambiente hospedado? A razão pela qual estou perguntando é porque executo um ambiente hospedado e estou tendo exatamente esse problema, em que ele destrói todo o pool de aplicativos. Eu preferiria que ele abortasse o encadeamento, onde ele pudesse voltar ao topo, e eu pudesse registrar o erro e continuar sem ter todos os encadeamentos do apppool eliminados.
Brain2000,

Starting with 2.0 ..., Estou curioso, o que os está impedindo de pegar SO e como foi possível 1.1(você mencionou isso no seu comentário)?
M.kazem Akhgary

47

O jeito certo é consertar o estouro, mas ....

Você pode obter uma pilha maior: -

using System.Threading;
Thread T = new Thread(threadDelegate, stackSizeInBytes);
T.Start();

Você pode usar a propriedade System.Diagnostics.StackTrace FrameCount para contar os quadros usados ​​e lançar sua própria exceção quando um limite de quadro for atingido.

Ou você pode calcular o tamanho da pilha restante e lançar sua própria exceção quando cair abaixo de um limite: -

class Program
{
    static int n;
    static int topOfStack;
    const int stackSize = 1000000; // Default?

    // The func is 76 bytes, but we need space to unwind the exception.
    const int spaceRequired = 18*1024; 

    unsafe static void Main(string[] args)
    {
        int var;
        topOfStack = (int)&var;

        n=0;
        recurse();
    }

    unsafe static void recurse()
    {
        int remaining;
        remaining = stackSize - (topOfStack - (int)&remaining);
        if (remaining < spaceRequired)
            throw new Exception("Cheese");
        n++;
        recurse();
    }
}

Basta pegar o queijo. ;)


47
Cheeseestá longe de ser específico. Eu iria parathrow new CheeseException("Gouda");
C.Evenhuis

13
@ C.Evenhuis Embora não haja dúvidas de que o Gouda é um queijo excepcional, deveria ser um RollingCheeseException ("Double Gloucester"). Veja cheese-rolling.co.uk

3
lol, 1) consertar não é possível porque sem pegá-lo você geralmente não sabe onde isso acontece 2) aumentar o tamanho da pilha é inútil com recursão infinita e 3) verificar a pilha no local certo é como o primeiro
Firo

2
mas sou intolerante à lactose
redoc

39

Na página do MSDN em StackOverflowException s:

Em versões anteriores do .NET Framework, seu aplicativo podia capturar um objeto StackOverflowException (por exemplo, para se recuperar de recursão ilimitada). No entanto, essa prática é atualmente desencorajada porque um código adicional significativo é necessário para capturar de forma confiável uma exceção de estouro de pilha e continuar a execução do programa.

A partir do .NET Framework versão 2.0, um objeto StackOverflowException não pode ser detectado por um bloco try-catch e o processo correspondente é encerrado por padrão. Consequentemente, os usuários são aconselhados a escrever seu código para detectar e evitar um estouro de pilha. Por exemplo, se seu aplicativo depende da recursão, use um contador ou uma condição de estado para encerrar o loop recursivo. Observe que um aplicativo que hospeda o common language runtime (CLR) pode especificar que o CLR descarregue o domínio do aplicativo onde ocorre a exceção de estouro de pilha e deixe o processo correspondente continuar. Para obter mais informações, consulte ICLRPolicyManager Interface e Hosting the Common Language Runtime.


23

Como vários usuários já disseram, você não pode pegar a exceção. No entanto, se você está lutando para descobrir onde está acontecendo, você pode configurar o visual studio para quebrar quando for lançado.

Para fazer isso, você precisa abrir as Configurações de exceção no menu 'Depurar'. Em versões mais antigas do Visual Studio, isso está em 'Debug' - 'Exceptions'; nas versões mais recentes, está em 'Debug' - 'Windows' - 'Configurações de exceção'.

Depois de abrir as configurações, expanda 'Common Language Runtime Exceptions', expanda 'System', role para baixo e marque 'System.StackOverflowException'. Em seguida, você pode examinar a pilha de chamadas e procurar o padrão de repetição das chamadas. Isso deve dar uma ideia de onde procurar para consertar o código que está causando o estouro da pilha.


1
Onde está Debug - exceções no VS 2015?
FrenkyB

1
Depurar - Windows - Configurações de exceção
Simon

15

Conforme mencionado acima várias vezes, não é possível capturar uma StackOverflowException que foi gerada pelo sistema devido a um estado de processo corrompido. Mas há uma maneira de perceber a exceção como um evento:

http://msdn.microsoft.com/en-us/library/system.appdomain.unhandledexception.aspx

A partir do .NET Framework versão 4, esse evento não é gerado para exceções que corrompem o estado do processo, como estouro de pilha ou violações de acesso, a menos que o manipulador de eventos seja crítico para a segurança e tenha o atributo HandleProcessCorruptedStateExceptionsAttribute.

No entanto, seu aplicativo será encerrado após sair da função de evento (uma solução MUITO suja, era reiniciar o aplicativo dentro deste evento haha, não fiz isso e nunca farei). Mas é bom o suficiente para registrar!

No .NET Framework versões 1.0 e 1.1, uma exceção não tratada que ocorre em um thread diferente do thread do aplicativo principal é capturada pelo tempo de execução e, portanto, não causa o encerramento do aplicativo. Portanto, é possível que o evento UnhandledException seja gerado sem que o aplicativo seja encerrado. A partir do .NET Framework versão 2.0, este backstop para exceções não tratadas em threads filho foi removido, porque o efeito cumulativo de tais falhas silenciosas incluía degradação de desempenho, dados corrompidos e travamentos, todos difíceis de depurar. Para obter mais informações, incluindo uma lista de casos em que o tempo de execução não termina, consulte Exceções em threads gerenciados.


6

Sim, no CLR 2.0, o estouro de pilha é considerado uma situação irrecuperável. Portanto, o tempo de execução ainda encerra o processo.

Para obter detalhes, consulte a documentação http://msdn.microsoft.com/en-us/library/system.stackoverflowexception.aspx


No CLR 2.0, a StackOverflowExceptionencerra o processo por padrão.
Brian Rasmussen

Não. Você pode capturar OOM e, em alguns casos, pode fazer sentido fazê-lo. Não sei o que quer dizer com desaparecimento do fio. Se um thread tiver uma exceção não tratada, o CLR encerrará o processo. Se o seu tópico completar seu método, ele será limpo.
Brian Rasmussen

5

Você não pode. O CLR não vai deixar você. Um estouro de pilha é um erro fatal e não pode ser recuperado.


Então, como fazer um Teste de Unidade falhar para essa exceção se, em vez de ser capturável, ele travar o executor do teste de unidade?
BrainSlugs83

1
@ BrainSlugs83. Você não sabe, porque é uma ideia boba. Por que você está testando se o seu código falha com uma StackOverflowException mesmo assim? O que acontece se o CLR mudar para que possa lidar com uma pilha mais profunda? O que acontece se você chamar sua função de unidade testada em algum lugar que já tenha uma pilha profundamente aninhada? Parece algo que não pode ser testado. Se você estiver tentando lançá-lo manualmente, escolha uma exceção melhor para a tarefa.
Matthew Scharley de

5

Você não pode, como a maioria das postagens está explicando, deixe-me adicionar outra área:

Em muitos sites você encontrará pessoas dizendo que a maneira de evitar isso é usar um AppDomain diferente, portanto, se isso acontecer, o domínio será descarregado. Isso é absolutamente errado (a menos que você hospede seu CLR), pois o comportamento padrão do CLR irá gerar um evento KillProcess, derrubando seu AppDomain padrão.


3

É impossível, e por um bom motivo (por exemplo, pense em todos aqueles catch (Exception) {} ao redor).

Se você deseja continuar a execução após o estouro da pilha, execute o código perigoso em um AppDomain diferente. As políticas CLR podem ser definidas para encerrar o AppDomain atual em estouro sem afetar o domínio original.


2
As instruções "catch" não seriam realmente um problema, pois no momento em que uma instrução catch pudesse ser executada, o sistema teria revertido os efeitos de tudo o que havia tentado usar muito espaço na pilha. Não há razão para capturar exceções de estouro de pilha ser perigoso. O motivo pelo qual essas exceções não podem ser capturadas é que permitir que sejam capturadas com segurança exigiria a adição de alguma sobrecarga extra a todo o código que usa a pilha, mesmo se ela não estourar.
supercat

4
Em algum momento, a afirmação não é bem pensada. Se você não consegue capturar o Stackoverflow, talvez nunca saiba ONDE isso aconteceu em um ambiente de produção.
Offler de
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.