programowanieProgramista desktopowy (WinForms, VB.NET)

Jak realizowana jest obsługa kolejek zdarzeń (event queue) i wywołań asynchronicznych w Visual Basic oraz jakie są różnice między różnymi metodami (DoEvents, BackgroundWorker, Async/Await)?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

W Visual Basic wywołania asynchroniczne i obsługa zdarzeń często realizowane są na różne sposoby, w zależności od wersji języka i typu aplikacji. W klasycznych aplikacjach WinForms VB.NET obsługa kolejki zdarzeń (event queue) zazwyczaj polega na wywołaniu Application.DoEvents(). Ta metoda pozwala obsłudze zdarzeń "zwolnić kontrolę", aby inne komunikaty mogły być obsługiwane, nie blokując głównego wątku:

While loading Application.DoEvents() 'Pozwala UI reagować na działania użytkownika End While

Do asynchronicznego wykonywania zadań w .NET z VB.NET używane są:

  • BackgroundWorker — komponent WinForms do przenoszenia długotrwałych operacji do osobnego wątku z bezpieczną interakcją z UI-wątkiem.
  • Słowa kluczowe Async/Await — wygodny nowoczesny system asynchroniczności, który pojawił się w .NET 4.5, pozwala pisać asynchroniczny kod "jak synchroniczny":
Public Async Function LoadDataAsync() As Task Dim result As String = Await GetWebDataAsync() TextBox1.Text = result End Function

Różnice:

  • DoEvents po prostu przetwarza kolejkę komunikatów UI i nie tworzy nowych wątków.
  • BackgroundWorker przenosi pracę do osobnego wątku, z wydarzeniem do bezpiecznego powrotu do UI-wątku.
  • Async/Await realizuje asynchroniczność, jednocześnie enkapsulując zwrot kontroli do UI-wątku.

Pytanie z pułapką.

W czym tkwi niebezpieczeństwo użycia DoEvents w pętli, jeśli trzeba wczytać plik z dysku i aktualizować ProgressBar?

Odpowiedź: DoEvents tymczasowo przekazuje kontrolę innym zdarzeniom Windows, ale nie zwalnia wątku. Jeśli używać go w ciężkich pętlach (np. wczytywanie dużego pliku z jednoczesnym aktualizowaniem interfejsu), mogą wystąpić nieoczekiwane błędy: wydarzenia użytkownika i zdarzenia myszy będą przetwarzane, co może prowadzić do ponownego uruchomienia przetwarzania lub nawet "zawieszenia" aplikacji. Jeśli zakłada się ciężkie ładowanie — należy użyć BackgroundWorker lub Task.

Przykład złego podejścia:

For i = 1 To 1000000 ProgressBar1.Value = i / 10000 Application.DoEvents() 'Nie zwalnia głównego wątku! Next

Historia

W praktyce był przypadek: na etapie migracji aplikacji VB6 do VB.NET do aktualizacji wskaźnika postępu używano DoEvents wewnątrz długotrwałej pętli wczytywania pliku. W efekcie interfejs "zawieszał się": jeśli użytkownik kliknął przycisk "Otwórz" jeszcze raz — uruchamiała się druga operacja wczytywania pliku, mieszając się z pierwszą, co prowadziło do zniekształcenia danych i awarii aplikacji.

Historia

W projekcie oprogramowania finansowego jeden z pracowników postanowił aktualizować wykres na formularzu za pomocą DoEvents w pętli, ponieważ nie znał BackgroundWorker. Podczas aktualizacji ogromnego zbioru punktów aplikacja zaczęła zawieszać się i ostatecznie "wylatywała" z powodu przepełnienia kolejki komunikatów Windows.

Historia

Używano asynchronicznego wywołania przez Async/Await, nie rozumiejąc, że długą pracę nie można uruchamiać w "gorącym" UI-wątku bez Await Task.Run(...). Rezultat: interfejs nie reagował na kliknięcia, a pasek postępu nie był aktualizowany, ponieważ długi metod wciąż wykonywał się na głównym wątku.