Zastosowanie Application.DoEvents ()

272

Może Application.DoEvents()być używany w C #?

Czy ta funkcja pozwala GUI dogonić resztę aplikacji, podobnie jak VB6 DoEvents?

Craig Johnston
źródło
35
DoEventsjest częścią Windows Forms, a nie językiem C #. Jako taki może być używany z dowolnego języka .NET. Nie należy go jednak używać z żadnego języka .NET.
Stephen Cleary
3
Jest rok 2017. Użyj Async / Await. Szczegółowe informacje można znaleźć na końcu odpowiedzi @hansPassant.
FastAl

Odpowiedzi:

468

Hmya, trwała mistyka DoEvent (). Nastąpił przeciwko temu olbrzymi luz, ale nikt tak naprawdę nie wyjaśnia, dlaczego jest „zły”. Ten sam rodzaj mądrości co „nie mutuj struktury”. Eee, dlaczego środowisko uruchomieniowe i język obsługują mutowanie struktury, jeśli jest tak źle? Z tego samego powodu: strzelasz sobie w stopę, jeśli nie zrobisz tego dobrze. Z łatwością. A robienie tego poprawnie wymaga dokładnej znajomości tego , co robi, co w przypadku DoEvent () zdecydowanie nie jest łatwe do zrozumienia.

Od samego początku: prawie każdy program Windows Forms faktycznie zawiera wywołanie DoEvents (). Jest sprytnie zamaskowany, ale pod inną nazwą: ShowDialog (). Jest to DoEvents (), który umożliwia modalne okno dialogowe, bez zawieszania reszty okien w aplikacji.

Większość programistów chce używać DoEvents, aby zatrzymać zamrażanie interfejsu użytkownika podczas pisania własnej pętli modalnej. Z pewnością to robi; wysyła wiadomości Windows i odbiera wszystkie żądania malowania. Problem polega jednak na tym, że nie jest selektywny. Nie tylko wysyła wiadomości o farbie, ale także dostarcza wszystko inne.

I jest zestaw powiadomień, które powodują problemy. Pochodzą z około 3 stóp przed monitorem. Użytkownik może na przykład zamknąć okno główne, gdy uruchomiona jest pętla wywołująca DoEvents (). Działa, interfejs użytkownika zniknął. Ale twój kod się nie zatrzymał, nadal wykonuje pętlę. To źle. Bardzo bardzo źle.

Jest więcej: użytkownik może kliknąć ten sam element menu lub przycisk, który powoduje uruchomienie tej samej pętli. Teraz masz dwie zagnieżdżone pętle wykonujące DoEvents (), poprzednia pętla została zawieszona, a nowa pętla zaczyna się od zera. To może zadziałać, ale chłopcze szanse są niewielkie. Zwłaszcza, gdy kończy się zagnieżdżona pętla i wznawia się zawieszoną, próbując zakończyć zadanie, które zostało już ukończone. Jeśli to nie bombarduje wyjątkiem, to na pewno dane zostaną zaszyfrowane do piekła.

Powrót do ShowDialog (). Wykonuje DoEvents (), ale pamiętaj, że robi coś innego. To wyłącza wszystkie okna w aplikacji , innych niż okna. Teraz, gdy problem 3 stóp został rozwiązany, użytkownik nie może nic zrobić, aby popsuć logikę. Rozwiązano zarówno tryby awarii zamykania okna, jak i ponownego uruchamiania zadania. Innymi słowy, użytkownik nie może ustawić kodu uruchamiającego program w innej kolejności. Będzie działał przewidywalnie, tak jak podczas testowania kodu. To sprawia, że ​​dialogi są wyjątkowo denerwujące; kto nie nienawidzi aktywnego okna dialogowego i niemożności kopiowania i wklejania czegoś z innego okna? Ale to jest cena.

Właśnie to wymaga bezpiecznego korzystania z DoEvent w twoim kodzie. Ustawienie właściwości Enabled wszystkich formularzy na false to szybki i skuteczny sposób na uniknięcie problemów. Oczywiście żaden programista nigdy nie lubi tego robić. I nie ma. Dlatego nie powinieneś używać DoEvents (). Powinieneś używać wątków. Mimo że dają ci pełny arsenał sposobów strzelania stopą w kolorowe i nieprzeniknione sposoby. Ale z tą zaletą, że strzelasz tylko własną stopą; nie pozwala (zwykle) jej strzelać.

Kolejne wersje C # i VB.NET zapewnią inną broń dzięki nowym słowom kluczowym wyczekiwania i asynchronizacji. Zainspirowany w części kłopotami spowodowanymi przez DoEvent i wątki, ale w dużej mierze przez interfejs API WinRT, który wymaga aktualizacji interfejsu użytkownika podczas operacji asynchronicznej. Jak czytanie z pliku.

Hans Passant
źródło
1
To jednak tylko wierzchołek lodowej góry. Używam Application.DoEventsbez problemów, dopóki nie dodałem niektórych funkcji UDP do mojego kodu, co spowodowało opisany tutaj problem . Chciałbym wiedzieć, czy istnieje sposób wokół , że z DoEvents.
darda
to ładna odpowiedź, jedyne, czego mi brakuje, to wyjaśnienie / dyskusja na temat wykonania i bezpiecznego projektowania wątków lub generowania czasu w ogóle - ale to znowu trzy razy więcej tekstu. :)
jheriko
5
Przydałoby się bardziej praktyczne rozwiązanie niż ogólnie „używanie wątków”. Na przykład BackgroundWorkerkomponent zarządza wątkami za Ciebie, unikając większości kolorowych efektów strzelania do stóp i nie wymaga najnowocześniejszych wersji językowych C #.
Ben Voigt
29

Może być, ale to hack.

Zobacz Czy DoEvents Evil? .

Bezpośrednio ze strony MSDN , do której odwoływało się urządzenie:

Wywołanie tej metody powoduje zawieszenie bieżącego wątku podczas przetwarzania wszystkich komunikatów w oknie oczekiwania. Jeśli komunikat powoduje wyzwolenie zdarzenia, mogą zostać wykonane inne obszary kodu aplikacji. Może to spowodować, że aplikacja będzie wykazywać nieoczekiwane zachowania, które są trudne do debugowania. Jeśli wykonujesz operacje lub obliczenia, które zajmują dużo czasu, często lepiej jest wykonać te operacje w nowym wątku. Aby uzyskać więcej informacji na temat programowania asynchronicznego, zobacz Omówienie programowania asynchronicznego.

Microsoft ostrzega więc przed jego użyciem.

Ponadto uważam, że jest to hack, ponieważ jego zachowanie jest nieprzewidywalne i podatne na skutki uboczne (wynika to z doświadczenia w próbie użycia DoEvent zamiast rozwijania nowego wątku lub korzystania z pracownika w tle).

Nie ma tutaj machismo - gdyby działało jako solidne rozwiązanie, to bym to zrobił. Jednak próba użycia DoEvents w .NET spowodowała tylko ból.

RQDQ
źródło
1
Warto zauważyć, że ten post pochodzi z 2004 r., Wcześniej .NET 2.0 i BackgroundWorkerpomógł uprościć „prawidłowy” sposób.
Justin
Zgoda. Również biblioteka zadań w .NET 4.0 jest całkiem przyjemna.
RQDQ,
1
Dlaczego miałby to być hack, gdyby Microsoft udostępnił funkcję właśnie w tym celu, do którego jest często potrzebny?
Craig Johnston
1
@Craig Johnston - zaktualizowałem moją odpowiedź, aby dokładniej wyjaśnić, dlaczego uważam, że DoEvent należy do kategorii hakerów.
RQDQ
Ten kodujący artykuł grozy nazwał go „spakowaniem DoEvent”. Znakomity!
gonzobrains
24

Tak, istnieje statyczna metoda DoEvents w klasie Application w przestrzeni nazw System.Windows.Forms. System.Windows.Forms.Application.DoEvents () można wykorzystać do przetwarzania komunikatów oczekujących w kolejce w wątku interfejsu użytkownika podczas wykonywania długotrwałego zadania w wątku interfejsu użytkownika. Ma to tę zaletę, że interfejs użytkownika wydaje się bardziej responsywny i nie „blokowany” podczas wykonywania długiego zadania. Jednak prawie zawsze NIE jest to najlepszy sposób na robienie rzeczy. Według firmy Microsoft wywołującej DoEvents „... powoduje zawieszenie bieżącego wątku podczas przetwarzania wszystkich komunikatów w oknie oczekiwania”. Wyzwolenie zdarzenia może spowodować nieoczekiwane i sporadyczne błędy, które są trudne do wyśledzenia. Jeśli masz rozległe zadanie, zdecydowanie lepiej jest to zrobić w osobnym wątku. Uruchamianie długich zadań w osobnym wątku pozwala na ich przetwarzanie bez zakłócania płynnego działania interfejsu użytkownika. Popatrztutaj po więcej szczegółów.

Oto przykład korzystania z DoEvent; zwróć uwagę, że firma Microsoft ostrzega również przed korzystaniem z niej.

Bill W.
źródło
13

Z mojego doświadczenia radziłbym wielką ostrożność przy korzystaniu z DoEvent w .NET. Doświadczyłem bardzo dziwnych wyników podczas używania DoEvent w TabControl zawierającym DataGridViews. Z drugiej strony, jeśli masz do czynienia tylko z małą postacią z paskiem postępu, może być w porządku.

Najważniejsze jest to: jeśli zamierzasz używać DoEvents, musisz dokładnie go przetestować przed wdrożeniem aplikacji.

Craig Johnston
źródło
Dobra odpowiedź, ale jeśli mogę zasugerować rozwiązanie dla paska postępu, które jest lepsze , powiedziałbym, wykonuj swoją pracę w osobnym wątku, udostępnij wskaźnik postępu w zmiennej zmiennej, blokowanej i odśwież pasek postępu od timera . W ten sposób żaden koder serwisowy nie ma ochoty dodawać ciężkiego kodu do swojej pętli.
mg30rg
1
DoEvent lub równoważny jest nieunikniony, jeśli masz dużo procesów interfejsu użytkownika i nie chcesz blokować interfejsu. Pierwszą opcją jest nie wykonywanie dużych porcji przetwarzania interfejsu użytkownika, ale może to sprawić, że kod będzie mniej konserwowalny. Jednak czekaj na Dispatcher.Yield () robi coś bardzo podobnego do DoEvents i może zasadniczo pozwolić na wykonanie procesu interfejsu użytkownika, który blokuje ekran, dla wszystkich celów i asynchronizacji.
Melbourne Developer
11

Tak.

Jeśli jednak musisz użyć Application.DoEvents, jest to głównie oznaka złego projektu aplikacji. Może zamiast tego chciałbyś popracować w osobnym wątku?

Frederik Gheysels
źródło
3
co jeśli chcesz, abyś mógł się obrócić i poczekać na pracę do ukończenia w innym wątku?
jheriko
@jheriko Więc naprawdę powinieneś spróbować async-czekać.
mg30rg
5

Widziałem powyższy komentarz Jheriko i początkowo zgodziłem się, że nie mogę znaleźć sposobu na uniknięcie korzystania z DoEvent, jeśli w końcu zakręcisz główny wątek interfejsu użytkownika, czekając na długo działający asynchroniczny fragment kodu w innym wątku. Ale z odpowiedzi Matthiasa proste odświeżenie małego panelu w moim interfejsie użytkownika może zastąpić DoEvent (i uniknąć nieprzyjemnego efektu ubocznego).

Więcej szczegółów na temat mojej sprawy ...

Robiłem następujące (zgodnie z sugestią tutaj ), aby upewnić się, że ekran powitalny typu paska postępu ( Jak wyświetlić nakładkę „ładującą” ... ) został zaktualizowany podczas długotrwałego polecenia SQL:

IAsyncResult asyncResult = sqlCmd.BeginExecuteNonQuery();
while (!asyncResult.IsCompleted)  //UI thread needs to Wait for Async SQL command to return
{
      System.Threading.Thread.Sleep(10); 
      Application.DoEvents();  //to make the UI responsive
}

Złe: dla mnie wywołanie DoEvent oznaczało, że kliknięcia myszy czasami strzelały do ​​formularzy za moim ekranem powitalnym, nawet jeśli zrobiłem to TopMost.

Dobrą / odpowiedź: Zamień linię DoEvents z prostym Refresh wywołania małego panelu w centrum mojego ekranu powitalnego, FormSplash.Panel1.Refresh(). Interfejs użytkownika ładnie się aktualizuje, a dziwność DoEvent, o której inni ostrzegali, zniknęła.

TamW
źródło
3
Odśwież nie aktualizuje jednak okna. Jeśli użytkownik wybierze inne okno na pulpicie, kliknięcie z powrotem do okna nie przyniesie żadnego efektu, a system operacyjny wyświetli aplikację jako niereagującą. DoEvents () robi znacznie więcej niż odświeżanie, ponieważ współdziała z systemem operacyjnym za pośrednictwem systemu przesyłania wiadomości.
ThunderGr,
4

Widziałem wiele aplikacji komercyjnych, wykorzystujących „DoEvents-Hack”. Zwłaszcza gdy renderowanie wchodzi w grę, często widzę to:

while(running)
{
    Render();
    Application.DoEvents();
}

Wszyscy wiedzą o złu tej metody. Jednak używają hacka, ponieważ nie znają żadnego innego rozwiązania. Oto niektóre podejścia zaczerpnięte z postu na blogu autorstwa Toma Millera :

  • Ustaw formularz tak, aby wszystkie rysunki występowały w WmPaint i wykonaj tam rendering. Przed końcem metody OnPaint upewnij się, że wykonujesz this.Invalidate (); Spowoduje to natychmiastowe uruchomienie metody OnPaint.
  • P / Invoke do Win32 API i wywołaj PeekMessage / TranslateMessage / DispatchMessage. (Doevents faktycznie robi coś podobnego, ale możesz to zrobić bez dodatkowych przydziałów).
  • Napisz własną klasę formularzy, która jest małym opakowaniem wokół CreateWindowEx, i daj sobie pełną kontrolę nad pętlą komunikatów. - Zdecyduj, że metoda DoEvents działa dobrze i trzymaj się jej.
Matthias
źródło
3

DoEvents pozwala użytkownikowi klikać lub wpisywać i wyzwalać inne zdarzenia, a wątki w tle są lepszym podejściem.

Nadal jednak zdarzają się przypadki, w których mogą wystąpić problemy wymagające opróżnienia komunikatów o zdarzeniach. Wystąpił problem polegający na tym, że formant RichTextBox ignorował metodę ScrollToCaret (), gdy formant miał w kolejce komunikaty do przetworzenia.

Poniższy kod blokuje wszystkie dane wejściowe użytkownika podczas wykonywania DoEvents:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace Integrative.Desktop.Common
{
    static class NativeMethods
    {
        #region Block input

        [DllImport("user32.dll", EntryPoint = "BlockInput")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool BlockInput([MarshalAs(UnmanagedType.Bool)] bool fBlockIt);

        public static void HoldUser()
        {
            BlockInput(true);
        }

        public static void ReleaseUser()
        {
            BlockInput(false);
        }

        public static void DoEventsBlockingInput()
        {
            HoldUser();
            Application.DoEvents();
            ReleaseUser();
        }

        #endregion
    }
}
cat_in_hat
źródło
1
Powinieneś zawsze blokować zdarzenia, dzwoniąc do doevents. W przeciwnym razie inne zdarzenia w aplikacji zostaną uruchomione w odpowiedzi, a aplikacja może zacząć robić dwie rzeczy naraz.
FastAl
2

Application.DoEvents może powodować problemy, jeśli w kolejce komunikatów zostanie umieszczone coś innego niż przetwarzanie grafiki.

Może to być przydatne do aktualizowania pasków postępu i powiadamiania użytkownika o postępach w budowaniu i ładowaniu MainForm, jeśli zajmie to trochę czasu.

W mojej niedawno utworzonej aplikacji użyłem DoEvents do aktualizacji niektórych etykiet na ekranie ładowania za każdym razem, gdy blok kodu jest wykonywany w konstruktorze mojego MainForm. Wątek interfejsu użytkownika był w tym przypadku zajęty wysyłaniem wiadomości e-mail na serwerze SMTP, który nie obsługiwał wywołań SendAsync (). Prawdopodobnie mógłbym utworzyć inny wątek za pomocą metod Begin () i End () i wywołać Send () z nich, ale ta metoda jest podatna na błędy i wolałbym, aby główna forma mojej aplikacji nie zgłaszała wyjątków podczas budowy.

Gość123
źródło