Dlaczego Environment.Exit () nie kończy już programu?


134

To jest coś, co odkryłem zaledwie kilka dni temu, otrzymałem potwierdzenie, że nie ogranicza się to tylko do mojej maszyny z tego pytania .

Najłatwiejszym sposobem odtworzenia tego jest uruchomienie aplikacji Windows Forms, dodanie przycisku i napisanie tego kodu:

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

Program kończy się niepowodzeniem po wykonaniu instrukcji Exit (). W Windows Forms pojawia się komunikat „Błąd podczas tworzenia uchwytu okna”.

Włączenie niezarządzanego debugowania sprawia, że ​​jest nieco jasne, co się dzieje. COM pętla modalna jest wykonywany i pozwala na wiadomość WM_PAINT mają być dostarczone. To śmiertelne w przypadku wyrzuconej postaci.

Jedyne fakty, które do tej pory zebrałem, to:

  • Nie ogranicza się tylko do uruchomienia z debugerem. To również się nie powiedzie bez jednego. Raczej kiepsko, okno dialogowe awarii WER pojawia się dwukrotnie .
  • Nie ma to nic wspólnego z drobiazgowością procesu. Warstwa wow64 jest dość znana, ale kompilacja AnyCPU ulega awarii w ten sam sposób.
  • Nie ma to nic wspólnego z tą samą awarią wersji .NET, 4.5 i 3.5.
  • Kod wyjścia nie ma znaczenia.
  • Wywołanie Thread.Sleep () przed wywołaniem Exit () nie naprawia tego.
  • Dzieje się tak w 64-bitowej wersji systemu Windows 8 i wydaje się, że nie ma to takiego samego wpływu na system Windows 7.
  • To powinno być stosunkowo nowe zachowanie, nie widziałem tego wcześniej. Nie widzę odpowiednich aktualizacji dostarczanych za pośrednictwem usługi Windows Update , chociaż historia aktualizacji nie jest już dokładna na moim komputerze.
  • To jest rażąco łamiące zachowanie. Możesz napisać taki kod w procedurze obsługi zdarzeń dla AppDomain.UnhandledException i ulega awarii w ten sam sposób.

Jestem szczególnie zainteresowany tym, co możesz zrobić, aby uniknąć tej awarii. Szczególnie scenariusz AppDomain.UnhandledException mnie przytłacza; nie ma wielu sposobów na zakończenie programu .NET. Należy pamiętać, że wywołanie Application.Exit () lub Form.Close () nie jest poprawne w procedurze obsługi zdarzeń dla UnhandledException, więc nie są one obejściem.


UPDATE: Mehrdad wskazał, że wątek finalizatora może być częścią problemu. Myślę, że widzę to i widzę również dowody na 2-sekundowy limit czasu, że CLR daje wątek finalizatora do zakończenia wykonywania.

Finalizator znajduje się w NativeWindow.ForceExitMessageLoop (). Jest tam funkcja IsWindow () Win32, która z grubsza odpowiada lokalizacji kodu, przesunięcie 0x3c podczas patrzenia na kod maszynowy w trybie 32-bitowym. Wygląda na to, że IsWindow () jest zakleszczona. Nie mogę uzyskać dobrego śladu stosu dla elementów wewnętrznych, jednak debuger uważa, że wywołanie P / Invoke właśnie zostało zwrócone. Trudno to wyjaśnić. Jeśli możesz uzyskać lepszy ślad stosu, chciałbym to zobaczyć. Kopalnia:

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

Nic powyżej wywołania ForceExitMessageLoop, włączony niezarządzany debugger.


2
Właśnie wypróbowałem to z .NET 4, 4 Client Profile, 3.5, 3.5 Client Profile, 3.0 i 2.0 i nie otrzymałem błędu na żadnym z nich. 64-bitowy Windows 7 to mój system operacyjny, używający VS2010.
Steve,

2
@Steve This happens on the 64-bit version of Windows 8Hans tak powiedział!
Parimal Raj

7
Mogę to powtórzyć (Win 8, 64-bity), skopiować / wkleić kod i podłączyć przycisk i otrzymuję dokładnie opisane objawy.
keyboardP,

3
Aplikacja w trybie konsoli nie mogła zademonstrować tego problemu, nic nie może pójść źle, gdy Exit () kontynuuje pompowanie wiadomości.
Hans Passant

3
Spotkałem się z tego rodzaju zachowaniem Exit(0)trochę temu z jakimś 64-bitowym Win7, zmiana ExitCodenie pomogła teraz, używając Process.GetCurrentProcess().Kill()bez problemu, działa
Sriram Sakthivel

Odpowiedzi:


85

Skontaktowałem się z firmą Microsoft w sprawie tego problemu i wydawało się, że to się opłaciło. Przynajmniej tak mi się wydaje :). Chociaż nie otrzymałem od nich potwierdzenia rozwiązania, z grupą Windows trudno się bezpośrednio skontaktować i musiałem skorzystać z pośrednika.

Aktualizacja dostarczona za pośrednictwem witryny Windows Update rozwiązała problem. Zauważalne 2-sekundowe opóźnienie przed awarią już nie występuje, co zdecydowanie sugeruje, że zakleszczenie IsWindow () zostało rozwiązane. Program zamyka się czysto i niezawodnie. Aktualizacja zainstalowała poprawki dla Windows Defender, wdboot.sys, wdfilter.sys, tcpip.sys, rpcrt4.dll, uxtheme.dll, crypt32.dll i wintrust.dll

Uxtheme.dll jest dziwnym rozwiązaniem. Implementuje interfejs API do tworzenia motywów wizualnych i jest używany przez ten program testowy. Nie jestem pewien, ale to właśnie na nim biorę moje pieniądze, jako źródło problemu. Kopia w C: \ WINDOWS \ system32 ma numer wersji 6.2.9200.16660, utworzoną 14 sierpnia 2013 na moim komputerze.

Sprawa zamknięta.


11
Historia Windows Update nie jest już dokładna na moim komputerze. Wiem tylko, że został zainstalowany 14 sierpnia.
Hans Passant,

51

Nie wiem, dlaczego to już „nie działa , ale myślę, że Environment.Exitwykonuje oczekujące finalizatory. Environment.FailFastnie.

Możliwe, że (z jakiegoś dziwnego powodu) masz dziwne oczekujące finalizatory, które muszą działać później, powodując to.


2
Możesz być na czymś. Finalizator jest zajęty wykonywaniem NativeWindow.ForceExitMessageLoop (). Co dziwne, nie jest zagnieżdżony w żadnym wywołaniu.
Hans Passant

@HansPassant: Chciałbym móc powtórzyć problem, żebym mógł się temu przyjrzeć, ale nie mogę. Czy wywołanie NativeWindow.ForceExitMessageLooputknęło w kodzie zarządzanym lub niezarządzanym? Czy w ogóle utknął, czy jest zajęty czekaniem lub czekaniem na wiadomość lub coś innego?
user541686

To z pewnością wydaje się wskazywać na podstawowy problem. Myślę, że przyczyną problemu jest funkcja IsWindow () winapi. Myślę, że widzę również 2-sekundowy limit czasu w wątku finalizatora, po którym wszystko idzie do diabła. Debugger nie pokazuje, jak wykonuje wywołanie IsWindow (), ale widziałem już wcześniej, jak Windows robi sztuczki ze stosem, wyłączając go podczas wprowadzania krytycznego kodu w systemie Windows.
Hans Passant

4
Myślę, że metoda Environment.FailFast (), dla danego przypadku nieobsługiwanych wyjątków, i tak jest prawdopodobnie najlepszą metodą do użycia. (Nie byłem tego świadomy - dzięki!) Jednak istnieje wiele starszego kodu, który używałby kodu Environment.Exit (), który niestety zawiesi się niezręcznie :(
Ian Yates

2
Zdecydowanie jesteś na czymś. W moim przypadku uruchomiłem IHost przy użyciu IHost.StartAsync do przeprowadzenia testów integracji, a mimo to po wywołaniu (i oczywiście oczekiwaniu) IHost.StopAsync proces nadal się nie zakończył. Dopiero po wywołaniu IHost.Dispose proces kończy się. Dziękuję za podpowiedź
Malte R

6

To nie wyjaśnia, dlaczego tak się dzieje, ale nie wywołałbym Environment.Exitprogramu obsługi zdarzeń przycisku, takiego jak twoja próbka - zamiast tego zamknij główny formularz, jak zasugerowano w odpowiedzi rene .

A jeśli chodzi o program AppDomain.UnhandledExceptionobsługi, może mógłbyś po prostu ustawić Environment.ExitCodezamiast dzwonić Environment.Exit.

Nie jestem pewien, co próbujesz tutaj osiągnąć. Dlaczego chcesz zwrócić kod zakończenia z aplikacji Windows Forms? Kody zakończenia są zwykle używane przez aplikacje konsolowe.

Szczególnie interesuje mnie to, co można zrobić, aby uniknąć tej awarii. Calling Environment.Exit () jest wymagane, aby zapobiec wyświetlaniu okna dialogowego WER.

Czy masz próbę / złapanie w metodzie głównej? W przypadku aplikacji Windows Forms zawsze mam try / catch wokół pętli komunikatów, a także nieobsługiwane programy obsługi wyjątków.


Całkiem pewny, że Application.Exitzamiast tego powinieneś zadzwonić Environment.Exit.
user541686

7
Przepraszamy, to nie jest obejście. Wywołanie Environment.Exit () jest wymagane, aby zapobiec wyświetlaniu okna dialogowego WER. Zwróć też uwagę na „znany fakt”, kod zakończenia nie ma znaczenia.
Hans Passant

7
@Hans: czy przechwytywanie AppDomain.UnhandledException w celu uniknięcia okna dialogowego WER jest uzasadnione w pierwszej kolejności? To znaczy, jeśli istnieje nieobsługiwany wyjątek, okno dialogowe WER powinno się wyświetlić, prawda?
Harry Johnston,

2

Znalazłem ten sam problem w naszej aplikacji, rozwiązaliśmy go za pomocą następującej konstrukcji:

Environment.ExitCode=1;
Application.Exit();
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.