Por que Environment.Exit () não encerra mais o programa?

134

Isso é algo que eu descobri há apenas alguns dias, e recebi a confirmação de que não está limitado apenas à minha máquina com essa pergunta .

A maneira mais fácil de reproduzi-lo é iniciando um aplicativo Windows Forms, adicione um botão e escreva este código:

    private void button1_Click(object sender, EventArgs e) {
        MessageBox.Show("yada");
        Environment.Exit(1);         // Kaboom!
    }

O programa falha após a execução da instrução Exit (). No Windows Forms, você recebe "Erro ao criar identificador de janela".

A ativação da depuração não gerenciada torna um pouco claro o que está acontecendo. O loop modal COM está em execução e permite que uma mensagem WM_PAINT seja entregue. Isso é fatal em um formulário descartado.

Os únicos fatos que reuni até agora são:

  • Não se limita apenas à execução com o depurador. Isso também falha sem um. Um tanto ruim quanto, a caixa de diálogo de falha do WER aparece duas vezes .
  • Não tem nada a ver com o testemunho do processo. A camada wow64 é bastante notória, mas uma compilação AnyCPU falha da mesma maneira.
  • Não tem nada a ver com a versão .NET, 4.5 e 3.5 travar da mesma maneira.
  • O código de saída não importa.
  • Chamar Thread.Sleep () antes de chamar Exit () não o corrige.
  • Isso acontece na versão de 64 bits do Windows 8 e o Windows 7 não parece ser afetado da mesma maneira.
  • Este deve ser um comportamento relativamente novo, eu nunca vi isso antes. Não vejo atualizações relevantes fornecidas pelo Windows Update , embora o histórico de atualizações não seja mais preciso na minha máquina.
  • Este é um comportamento grosseiramente violento. Você escreveria um código como esse em um manipulador de eventos para AppDomain.UnhandledException e ele trava da mesma maneira.

Estou particularmente interessado no que você poderia fazer para evitar esse acidente. Particularmente, o cenário AppDomain.UnhandledException me surpreende; não há muitas maneiras de encerrar um programa .NET. Observe que chamar Application.Exit () ou Form.Close () não é válido em um manipulador de eventos para UnhandledException, portanto, não são soluções alternativas.


ATUALIZAÇÃO: Mehrdad apontou que o encadeamento do finalizador pode ser parte do problema. Eu acho que estou vendo isso e também estou vendo algumas evidências pelo tempo limite de 2 segundos de que o CLR fornece ao thread do finalizador para concluir a execução.

O finalizador está dentro de NativeWindow.ForceExitMessageLoop (). Existe uma função IsWindow () Win32 que corresponde aproximadamente à localização do código, desloca 0x3c ao olhar o código da máquina no modo de 32 bits. Parece que IsWindow () está em um impasse. No entanto, não consigo obter um bom rastreamento de pilha para os internos, o depurador acha que a chamada P / Invoke acabou de retornar. Isso é difícil de explicar. Se você pode obter um melhor rastreamento de pilha, eu adoraria vê-lo. Meu:

System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.ForceExitMessageLoop() + 0x3c bytes
System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.Finalize() + 0x16 bytes
[Native to Managed Transition]
kernel32.dll!@BaseThreadInitThunk@12()  + 0xe bytes
ntdll.dll!___RtlUserThreadStart@8()  + 0x27 bytes
ntdll.dll!__RtlUserThreadStart@8()  + 0x1b bytes

Nada acima da chamada ForceExitMessageLoop, o depurador não gerenciado ativado.

Hans Passant
fonte
2
Eu apenas tentei isso com o .NET 4, 4 Client Profile, 3.5, 3.5 Client Profile, 3.0 e 2.0 e não recebi um erro em nenhum deles. O Windows 7 de 64 bits é o meu sistema operacional, usando o VS2010.
21313 Steve Steve
2
@ Steve This happens on the 64-bit version of Windows 8Hans disse isso!
Parimal Raj
7
Posso reproduzir isso (Win 8, 64 bits), copiar / colar seu código e conectar um botão, e recebo os sintomas exatos descritos.
keyboardP
3
Um aplicativo no modo de console não pôde demonstrar esse problema; nada pode dar errado quando Exit () continua bombeando mensagens.
Hans Passant
3
Eu encontrei esse tipo de comportamento com Exit(0)um pouco atrás, com alguns 64bit Win7, mudando ExitCodenão ajudou usando agora Process.GetCurrentProcess().Kill()sem qualquer problema ele funciona
Sriram Sakthivel

Respostas:

85

Entrei em contato com a Microsoft sobre esse problema e isso parecia ter valido a pena. Pelo menos eu gostaria de pensar que sim :). Embora eu não tenha recebido uma confirmação de uma resolução deles, é difícil entrar em contato diretamente com o grupo Windows e eu tive que usar um intermediário.

Uma atualização entregue pelo Windows Update resolveu o problema. O atraso perceptível de 2 segundos antes da falha não está mais presente, sugerindo fortemente que o bloqueio do IsWindow () foi resolvido. E o programa é encerrado de forma limpa e confiável. A atualização instalou os patches para Windows Defender, wdboot.sys, wdfilter.sys, tcpip.sys, rpcrt4.dll, uxtheme.dll, crypt32.dll e wintrust.dll

Uxtheme.dll é o estranho-out. Ele implementa a API de temas do Visual Styles e é usado por este programa de teste. Não tenho certeza, mas meu dinheiro está nessa como a fonte do problema. A cópia em C: \ WINDOWS \ system32 possui o número da versão 6.2.9200.16660, criada em 14 de agosto de 2013 na minha máquina.

Caso encerrado.

Hans Passant
fonte
11
O histórico do Windows Update não é mais preciso na minha máquina. Tudo o que sei é que ele foi instalado em 14 de agosto.
Hans Passant
51

Não sei por que não funciona "mais" , mas acho que Environment.Exitexecuta finalizadores pendentes. Environment.FailFastnão.

Pode ser que (por algum motivo bizarro) você tenha finalizadores pendentes estranhos que devem ser executados posteriormente, fazendo com que isso aconteça.

user541686
fonte
2
Você pode estar no caminho certo. O finalizador está ocupado executando NativeWindow.ForceExitMessageLoop (). Estranhamente, não está aninhado em nenhuma ligação.
Hans Passant
@HansPassant: Eu gostaria de poder reprovar o problema para que eu pudesse investigar, mas não posso. A chamada está NativeWindow.ForceExitMessageLoopparada no código gerenciado ou não gerenciado? É mesmo preso, ou está ocupado esperando ou esperando por uma mensagem ou outra coisa?
user541686
Isso certamente parece apontar para o problema central. Eu acho que é a função winapi IsWindow () que está na raiz do problema. Acho que também estou vendo o tempo limite de 2 segundos no thread do finalizador, após o qual tudo vai para o inferno. O depurador não mostra a execução da chamada IsWindow (), mas já vi o Windows fazer truques com a pilha antes, trocando-a ao inserir código crítico dentro do Windows.
Hans Passant
4
Eu acho que o método Environment.FailFast (), para o caso específico de exceções não tratadas, é provavelmente o melhor método para usar de qualquer maneira. (Eu não estava ciente disso - obrigado!) No entanto, há um monte de código legado que usaria Environment.Exit (), que falhará desajeitadamente, infelizmente :(
Ian Yates
2
Você definitivamente está decidido a algo. No meu caso, eu havia iniciado um IHost usando o IHost.StartAsync para executar alguns testes de integração e, mesmo assim, depois de chamar (e, é claro, aguardar) o IHost.StopAsync, o processo ainda não foi encerrado. Somente após chamar IHost.Dispose, o processo termina. Obrigado pela dica
Malte R
6

Isso não explica por que está acontecendo, mas eu não chamaria Environment.Exitum manipulador de eventos de botão como o seu exemplo - em vez disso, feche o formulário principal conforme sugerido na resposta de rene .

Quanto a um AppDomain.UnhandledExceptionmanipulador, talvez você possa apenas definir, em Environment.ExitCodevez de ligar Environment.Exit.

Não tenho certeza do que você está tentando alcançar aqui. Por que você deseja retornar um código de saída de um aplicativo Windows Forms? Normalmente, os códigos de saída são usados ​​pelos aplicativos de console.

Estou particularmente interessado no que você poderia fazer para evitar essa falha. O Calling Environment.Exit () é necessário para impedir a exibição da caixa de diálogo WER.

Você tem uma tentativa / captura no método Main? Para aplicativos Windows Forms, eu sempre tenho um try / catch em torno do loop de mensagens, bem como os manipuladores de exceção não manipulados.

Joe
fonte
Tenho certeza que você deveria ligar em Application.Exitvez de Environment.Exit.
user541686
7
Desculpe, esta não é uma solução alternativa. É necessário chamar Environment.Exit () para impedir a exibição da caixa de diálogo WER. Observe o "fato conhecido" também, o código de saída não importa.
Hans Passant
7
@ Hans: está pegando AppDomain.UnhandledException para tentar evitar a caixa de diálogo WER legítima em primeiro lugar? Quero dizer, se houver uma exceção não tratada, a caixa de diálogo WER deve aparecer, não é?
Harry Johnston
2

Encontrei o mesmo problema em nosso aplicativo, resolvemos com a seguinte construção:

Environment.ExitCode=1;
Application.Exit();
Jesse
fonte