Pourquoi Environment.Exit () ne termine-t-il plus le programme?

134

C'est quelque chose que j'ai découvert il y a quelques jours à peine, j'ai eu la confirmation que cette question ne se limitait pas à ma machine .

Le moyen le plus simple de le reproduire est de démarrer une application Windows Forms, d'ajouter un bouton et d'écrire ce code:

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

Le programme échoue après l'exécution de l'instruction Exit (). Sur Windows Forms, vous obtenez "Erreur lors de la création de la poignée de fenêtre".

L'activation du débogage non géré montre quelque peu ce qui se passe. La boucle modale COM est en cours d'exécution et permet la remise d'un message WM_PAINT. C'est fatal sur une forme éliminée.

Les seuls faits que j'ai recueillis jusqu'à présent sont:

  • Ce n'est pas seulement limité à l'exécution avec le débogueur. Cela échoue également sans un. Plutôt mal aussi, la boîte de dialogue de crash WER apparaît deux fois .
  • Cela n'a rien à voir avec le bitness du processus. La couche wow64 est assez notoire, mais une construction AnyCPU plante de la même manière.
  • Cela n'a rien à voir avec la version .NET, 4.5 et 3.5 plantent de la même manière.
  • Le code de sortie n'a pas d'importance.
  • L'appel de Thread.Sleep () avant d'appeler Exit () ne résout pas le problème.
  • Cela se produit sur la version 64 bits de Windows 8 et Windows 7 ne semble pas être affecté de la même manière.
  • Cela devrait être un comportement relativement nouveau, je n'ai jamais vu cela auparavant. Je ne vois aucune mise à jour pertinente fournie via Windows Update , même si l'historique des mises à jour n'est plus précis sur ma machine.
  • C'est un comportement extrêmement cassant. Vous écririez du code comme celui-ci dans un gestionnaire d'événements pour AppDomain.UnhandledException, et il se bloque de la même manière.

Je suis particulièrement intéressé par ce que vous pourriez faire pour éviter ce crash. En particulier, le scénario AppDomain.UnhandledException me laisse perplexe; il n'y a pas beaucoup de façons de terminer un programme .NET. Veuillez noter que l'appel à Application.Exit () ou Form.Close () ne sont pas valides dans un gestionnaire d'événements pour UnhandledException, ils ne sont donc pas des solutions de contournement.


MISE À JOUR: Mehrdad a souligné que le fil de finalisation pourrait faire partie du problème. Je pense que je vois cela et que je vois également des preuves pour le délai de 2 secondes que le CLR donne au thread de finalisation pour terminer l'exécution.

Le finaliseur est dans NativeWindow.ForceExitMessageLoop (). Il y a là une fonction IsWindow () Win32 qui correspond à peu près à l'emplacement du code, offset 0x3c lorsque vous regardez le code machine en mode 32 bits. Il semble que IsWindow () est dans une impasse. Je ne peux pas obtenir une bonne trace de pile pour les éléments internes, cependant, le débogueur pense que l' appel P / Invoke vient de revenir. C'est difficile à expliquer. Si vous pouvez obtenir une meilleure trace de pile, j'adorerais la voir. Mien:

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

Rien au-dessus de l'appel ForceExitMessageLoop, débogueur non géré activé.

Hans Passant
la source
2
Je viens d'essayer cela avec .NET 4, 4 Client Profile, 3.5, 3.5 Client Profile, 3.0 et 2.0, et je n'ai reçu d'erreur sur aucun d'entre eux. Windows 7 64 bits est mon système d'exploitation, utilisant VS2010.
Steve
2
@Steve This happens on the 64-bit version of Windows 8Hans l'a dit!
Parimal Raj
7
Je peux reproduire ceci (Win 8, 64 bits), copier / coller votre code et câblé un bouton et j'obtiens les symptômes exacts décrits.
clavierP
3
Une application en mode console n'a pas pu démontrer ce problème, rien ne peut aller de travers lorsque Exit () continue de pomper des messages.
Hans Passant
3
J'ai rencontré ce genre de comportement il y a Exit(0)un peu avec un Win7 64 bits, Changer ExitCoden'aide pas maintenant à utiliser Process.GetCurrentProcess().Kill()sans aucun problème cela fonctionne
Sriram Sakthivel

Réponses:

85

J'ai contacté Microsoft à propos de ce problème et cela semble avoir porté ses fruits. Au moins j'aimerais penser que oui :). Bien que je n'ai pas reçu de confirmation d'une résolution de leur part, le groupe Windows est difficile à contacter directement et j'ai dû utiliser un intermédiaire.

Une mise à jour fournie via Windows Update a résolu le problème. Le délai notable de 2 secondes avant le crash n'est plus présent, suggérant fortement que l'impasse IsWindow () a été résolue. Et le programme s'arrête proprement et de manière fiable. La mise à jour a installé les correctifs pour Windows Defender, wdboot.sys, wdfilter.sys, tcpip.sys, rpcrt4.dll, uxtheme.dll, crypt32.dll et wintrust.dll

Uxtheme.dll est un canard étrange. Il implémente l'API de thématisation des styles visuels et est utilisé par ce programme de test. Je ne peux pas en être sûr, mais mon argent est sur celui-là comme source du problème. La copie dans C: \ WINDOWS \ system32 a le numéro de version 6.2.9200.16660, créé le 14 août 2013 sur ma machine.

Affaire classée.

Hans Passant
la source
11
L'historique de Windows Update n'est plus précis sur ma machine. Tout ce que je sais, c'est qu'il a été installé le 14 août.
Hans Passant
51

Je ne sais pas pourquoi cela ne fonctionne "plus" , mais je pense qu'il Environment.Exitexécute les finaliseurs en attente. Environment.FailFastpas.

Il se peut que (pour une raison étrange) vous ayez d'étranges finaliseurs en attente qui doivent être exécutés par la suite, ce qui provoque ce problème.

user541686
la source
2
Vous êtes peut-être sur quelque chose. Le finaliseur est occupé à exécuter NativeWindow.ForceExitMessageLoop (). Curieusement, il n'est imbriqué dans aucun appel.
Hans Passant
@HansPassant: J'aurais aimé pouvoir reproduire le problème pour pouvoir l'examiner, mais je ne peux pas. L'appel à NativeWindow.ForceExitMessageLooprester bloqué dans du code géré ou non géré? Est-il même bloqué, ou est-il occupé à attendre ou à attendre un message ou autre chose?
user541686
Cela semble certainement indiquer le problème central. Je pense que c'est la fonction winapi IsWindow () qui est à l'origine du problème. Je pense que je vois également le délai de 2 secondes sur le fil de finalisation, après quoi tout va en enfer. Le débogueur ne montre pas qu'il exécute l'appel IsWindow (), mais j'ai déjà vu Windows jouer des tours avec la pile, la désactivant lors de la saisie de code critique dans Windows.
Hans Passant
4
Je pense que la méthode Environment.FailFast (), pour le cas donné d'exceptions non gérées, est probablement la meilleure méthode à utiliser de toute façon. (Je n'étais pas au courant - merci!) Cependant, il y a beaucoup de code hérité qui utiliserait Environment.Exit () qui plantera maladroitement malheureusement :(
Ian Yates
2
Vous êtes très certainement sur quelque chose. Dans mon cas, j'avais démarré un IHost en utilisant IHost.StartAsync pour effectuer des tests d'intégration, et pourtant après avoir appelé (et bien sûr en attente) IHost.StopAsync, le processus ne s'est toujours pas terminé. Ce n'est qu'après avoir appelé IHost.Dispose que le processus se termine. Merci pour la pointe
Malte R
6

Cela n'explique pas pourquoi cela se produit, mais je n'appellerais pas Environment.Exitun gestionnaire d'événements de bouton comme votre exemple - fermez plutôt le formulaire principal comme suggéré dans la réponse de rene .

En ce qui concerne un AppDomain.UnhandledExceptiongestionnaire, vous pourriez peut-être simplement définir Environment.ExitCodeplutôt que d'appeler Environment.Exit.

Je ne suis pas sûr de ce que vous essayez de réaliser ici. Pourquoi souhaitez-vous renvoyer un code de sortie à partir d'une application Windows Forms? Normalement, les codes de sortie sont utilisés par les applications de console.

Je suis particulièrement intéressé par ce que vous pourriez faire pour éviter ce crash. L'appel de Environment.Exit () est nécessaire pour empêcher l'affichage de la boîte de dialogue WER.

Avez-vous un essai / capture dans la méthode Main? Pour les applications Windows Forms, j'ai toujours un try / catch autour de la boucle de message ainsi que les gestionnaires d'exceptions non gérés.

Joe
la source
Je suis presque sûr que vous êtes censé appeler Application.Exitau lieu de Environment.Exit.
user541686
7
Désolé, ce n'est pas une solution de contournement. L'appel de Environment.Exit () est nécessaire pour empêcher l'affichage de la boîte de dialogue WER. Notez également le "fait connu", le code de sortie n'a pas d'importance.
Hans Passant
7
@Hans: attraper AppDomain.UnhandledException pour essayer d'éviter le dialogue WER est-il légitime en premier lieu? Je veux dire, s'il y a une exception non gérée, la boîte de dialogue WER est censée s'afficher, n'est-ce pas?
Harry Johnston
2

J'ai trouvé le même problème dans notre application, nous l'avons résolu avec la construction suivante:

Environment.ExitCode=1;
Application.Exit();
Jesse
la source