BackgroundWorker a wątek tła

166

Mam stylistyczne pytanie dotyczące wyboru implementacji wątku w tle, którego powinienem użyć w aplikacji formularza Windows. Obecnie mam BackgroundWorkerna formularzu, który ma nieskończoną (while(true))pętlę. W tej pętli używam WaitHandle.WaitAnydrzemki wątku, dopóki nie wydarzy się coś interesującego. Jeden z uchwytów zdarzenia, na które czekam, to „ StopThread” zdarzenie, dzięki czemu mogę wyrwać się z pętli. To zdarzenie jest sygnalizowane, gdy z mojego zastąpienia Form.Dispose().

Czytałem gdzieś, które BackgroundWorkerjest naprawdę przeznaczone do operacji, z którymi nie chcesz wiązać interfejsu użytkownika i które mają skończony koniec - jak pobieranie pliku lub przetwarzanie sekwencji elementów. W tym przypadku „koniec” jest nieznany i tylko wtedy, gdy okno jest zamknięte. Czy zatem bardziej odpowiednie byłoby dla mnie użycie wątku w tle zamiast BackgroundWorkerdo tego celu?

Freddy Smith
źródło

Odpowiedzi:

88

Z mojego zrozumienia Twojego pytania wynika, że ​​używasz BackgroundWorkerstandardowego wątku.

Powodem, dla którego BackgroundWorkerjest zalecany w przypadku rzeczy, których nie chcesz wiązać wątku interfejsu użytkownika, jest to, że ujawnia kilka fajnych zdarzeń podczas tworzenia Win Forms.

Zdarzenia takie jak RunWorkerCompletedsygnalizowanie, kiedy wątek zakończył to, co musiał zrobić, oraz ProgressChangedzdarzenie aktualizowania interfejsu GUI w postępie wątków.

Więc jeśli ich nie używasz, nie widzę żadnej szkody w używaniu standardowego wątku do tego, co musisz zrobić.

ParmezanCodice
źródło
inna kwestia, której nie jestem pewien, to przypuszczenie, że próbuję pozbyć się formularza, na którym działa program w tle. Sygnalizuję shutdownevent (ManualResetEvent) i po pewnym czasie DoWork z wdziękiem się zamyka. Czy powinienem po prostu pozwolić formularzowi przejść dalej i wyrzucić, nawet jeśli DoWork może zająć trochę więcej czasu, aby zakończyć, czy jest jakiś sposób (i czy lepiej) na wątek. formularza kontynuować?
freddy smith
Myślę, że BackgroundWorker.IsBusy jest tym, czego szukasz.
ParmesanCodice
1
Używaj tylko CancelAsync(i sprawdzaj, CancellationPendingczy twój wątek będzie odpytywany w krótkich odstępach czasu, jeśli zamiast tego chcesz mieć zgłoszony wyjątek, użyj a, System.Threading.Thread.Abort()który wywołuje wyjątek w samym bloku wątku, wybierz model odpowiedni do sytuacji.
Brett Ryan
369

Niektóre z moich myśli ...

  1. Użyj BackgroundWorker, jeśli masz jedno zadanie, które działa w tle i wymaga interakcji z interfejsem użytkownika. Zadanie kierowania danych i wywołań metod do wątku interfejsu użytkownika jest obsługiwane automatycznie przez model oparty na zdarzeniach. Unikaj BackgroundWorker, jeśli ...
    • Twój zestaw nie ma lub nie współpracuje bezpośrednio z interfejsem użytkownika,
    • potrzebujesz wątku, aby był wątkiem pierwszego planu, lub
    • musisz manipulować priorytetem wątku.
  2. Użyj wątku ThreadPool, gdy wymagana jest wydajność. ThreadPool pomaga uniknąć narzutu związanego z tworzeniem, uruchamianiem i zatrzymywaniem wątków. Unikaj korzystania z puli wątków, jeśli ...
    • zadanie działa przez cały okres istnienia Twojej aplikacji,
    • potrzebujesz wątku na pierwszym planie,
    • musisz manipulować priorytetem wątku, lub
    • potrzebujesz wątku, aby miał ustaloną tożsamość (przerywanie, zawieszanie, odkrywanie).
  3. Użyj klasy Thread do długotrwałych zadań i gdy potrzebujesz funkcji oferowanych przez formalny model wątków, np. Wybieranie między wątkami pierwszego planu i wątki w tle, dostosowywanie priorytetu wątku, precyzyjna kontrola nad wykonywaniem wątków itp.
Matt Davis
źródło
10
Proces roboczy w tle znajduje się w obszarze nazw zestawu System.dll i System.ComponentModel. Nie ma zależności od Winforms.
Kugel
17
To prawda, ale BackgroundWorkerma na celu raportowanie postępu wątku zainteresowanej stronie, co zwykle obejmuje interfejs użytkownika. Dokumentacja MSDN dla tej klasy wyjaśnia to bardzo jasno. Jeśli po prostu potrzebujesz zadania do wykonania w tle, lepiej użyj ThreadPoolwątku.
Matt Davis
5
W odniesieniu do twojej uwagi dotyczącej System.Windows.Formszgromadzenia; BackgroundWorkerjest również przydatny w przypadku aplikacji WPF i te aplikacje mogą nie mieć odniesienia do WinForms.
GiddyUpHorsey,
12

Prawie to, co powiedział Matt Davis, z następującymi dodatkowymi punktami:

Dla mnie głównym wyróżnikiem BackgroundWorkerjest automatyczne kierowanie zakończonego zdarzenia za pośrednictwem SynchronizationContext. W kontekście interfejsu użytkownika oznacza to, że zakończone zdarzenie jest uruchamiane w wątku interfejsu użytkownika, a zatem może służyć do aktualizowania interfejsu użytkownika. Jest to główny wyróżnik, jeśli używasz BackgroundWorkerw kontekście interfejsu użytkownika.

Zadania wykonywane za pośrednictwem programu ThreadPoolnie mogą być łatwo anulowane (obejmuje to ThreadPool. QueueUserWorkItemI delegaci wykonują asynchronicznie). Tak więc, podczas gdy unika się narzutu związanego z rozpędzaniem wątku, jeśli potrzebujesz anulowania, użyj BackgroundWorkerlub (bardziej prawdopodobne poza interfejsem użytkownika) , podkręć wątek i zachowaj do niego odniesienie, aby móc wywołać Abort().

mola 7
źródło
1
Tylko ... miejmy nadzieję, że aplikacja została zaprojektowana z myślą o czystej metodzie zatrzymywania zadania z
11

Ponadto wiążesz wątek puli wątków na czas życia pracownika w tle, co może budzić niepokój, ponieważ jest ich tylko skończona liczba. Powiedziałbym, że jeśli kiedykolwiek tworzysz wątek tylko raz dla swojej aplikacji (i nie używasz żadnej z funkcji programu roboczego w tle), użyj wątku zamiast wątku pracującego w tle / puli wątków.

Matt
źródło
1
Myślę, że to dobra uwaga. Więc wiadomość, którą z tego wyciągnąłem, to użycie pracownika działającego w tle, jeśli potrzebujesz wątku w tle „tymczasowo” w czasie życia formularza, jednak jeśli potrzebujesz wątku w tle przez cały okres istnienia formularza (który może być minutami, godzinami, dni ...), a następnie użyj Thread zamiast BackgroundWorker, aby nie nadużywać celu ThreadPool
freddy smith
Odnośnie: „… co może budzić niepokój, ponieważ jest ich tylko skończona liczba”, czy masz na myśli inne aplikacje w systemie operacyjnym, które mogą ich potrzebować i udostępniać z tej samej „puli”?
Dan W
8

Wiesz, czasami po prostu łatwiej jest pracować z BackgroundWorkerem, niezależnie od tego, czy używasz Windows Forms, WPF czy jakiejkolwiek innej technologii. Fajną częścią tych facetów jest to, że masz wątki bez martwienia się zbytnio o to, gdzie jest wykonywany wątek, co jest świetne w przypadku prostych zadań.

Przed użyciem BackgroundWorkerrozważ najpierw, jeśli chcesz anulować wątek (zamknięcie aplikacji, anulowanie użytkownika), musisz zdecydować, czy Twój wątek powinien sprawdzać odwołania, czy też powinien być narzucony na samo wykonanie.

BackgroundWorker.CancelAsync()zostanie ustawiony CancellationPendingna, trueale nie zrobi nic więcej, wtedy wątki są odpowiedzialne za ciągłe sprawdzanie tego, pamiętaj również, że w tym podejściu możesz skończyć z sytuacją wyścigu, w której użytkownik anulował, ale wątek został ukończony przed testowaniem CancellationPending.

Thread.Abort() z drugiej strony zgłosi wyjątek w wykonaniu wątku, który wymusza anulowanie tego wątku, ale musisz uważać na to, co może być niebezpieczne, jeśli ten wyjątek został nagle zgłoszony podczas wykonywania.

Gwintowanie wymaga bardzo dokładnego rozważenia bez względu na zadanie, do dalszego czytania:

Programowanie równoległe w najlepszych rozwiązaniach dotyczących zarządzania wątkami .NET Framework

Brett Ryan
źródło
5

Wiedziałem, jak używać wątków, zanim poznałem .NET, więc zanim zacząłem używać BackgroundWorkers. Matt Davis podsumował różnicę z wielką doskonałością, ale dodałbym, że trudniej jest zrozumieć, co dokładnie robi kod, a to może utrudnić debugowanie. Łatwiej jest myśleć o tworzeniu i zamykaniu wątków, IMO, niż o przydzielaniu pracy puli wątków.

Nadal nie mogę komentować postów innych osób, więc wybacz mi chwilową kulawiznę w używaniu odpowiedzi na adres piers7

Nie używaj Thread.Abort();zamiast tego, sygnalizuj zdarzenie i zaprojektuj swój wątek tak, aby kończył się wdzięcznie, gdy zostanie zasygnalizowany. Thread.Abort()podnosi wartość ThreadAbortExceptionw dowolnym momencie wykonywania wątku, co może powodować różne nieszczęśliwe rzeczy, takie jak osierocone monitory, uszkodzony stan współdzielenia i tak dalej.
http://msdn.microsoft.com/en-us/library/system.threading.thread.abort.aspx

ajs410
źródło
2

Jeśli się nie zepsuł - napraw, aż będzie ... tylko żartuję :)

Ale poważnie, BackgroundWorker jest prawdopodobnie bardzo podobny do tego, co już masz, gdybyś zaczął z nim od początku, może zaoszczędziłbyś trochę czasu - ale w tym momencie nie widzę takiej potrzeby. Chyba że coś nie działa lub myślisz, że twój obecny kod jest trudny do zrozumienia, wtedy trzymałbym się tego, co masz.

Gandalf
źródło
2

Podstawowa różnica polega na tym, że, jak powiedziałeś, generowanie zdarzeń GUI z pliku BackgroundWorker. Jeśli wątek nie musi aktualizować wyświetlacza ani generować zdarzeń dla głównego wątku GUI, może to być prosty wątek.

David R. Tribble
źródło
2

Chcę zwrócić uwagę na jedno zachowanie klasy BackgroundWorker, o którym jeszcze nie wspomniano. Możesz sprawić, aby normalny wątek działał w tle, ustawiając właściwość Thread.IsBackground.

Wątki w tle są identyczne z wątkami pierwszego planu, z tą różnicą, że wątki w tle nie zapobiegają zakończeniu procesu. [ 1 ]

Możesz przetestować to zachowanie, wywołując następującą metodę w konstruktorze okna formularza.

void TestBackgroundThread()
{
    var thread = new Thread((ThreadStart)delegate()
    {
        long count = 0;
        while (true)
        {
            count++;
            Debug.WriteLine("Thread loop count: " + count);
        }
    });

    // Choose one option:
    thread.IsBackground = true; // <--- This will make the thread run in background
    thread.IsBackground = false; // <--- This will delay program termination

    thread.Start();
}

Gdy właściwość IsBackground jest ustawiona na true i zamkniesz okno, aplikacja zostanie normalnie zakończona.

Ale gdy właściwość IsBackground jest ustawiona na false (domyślnie) i zamkniesz okno, tylko okno zniknie, ale proces będzie nadal działał.

Klasa BackgroundWorker wykorzystuje Thread, który działa w tle.

Doomjunky
źródło
1

Pracujący w tle to klasa, która działa w oddzielnym wątku, ale zapewnia dodatkowe funkcje, których nie można uzyskać za pomocą prostego wątku (np. Obsługa raportów o postępach zadań).

Jeśli nie potrzebujesz dodatkowych funkcji zapewnianych przez pracownika w tle - a wydaje się, że nie - wtedy bardziej odpowiedni byłby wątek.

Cesar
źródło
-1

Wprawia mnie w zakłopotanie fakt, że projektant studia wizualnego pozwala tylko na używanie BackgroundWorkerów i Timerów, które w rzeczywistości nie działają z projektem usługi.

Zapewnia zgrabną kontrolę przeciągania i upuszczania do usługi, ale ... nawet nie próbuj jej wdrażać. Nie zadziała.

Usługi: używaj tylko System.Timers.Timer System.Windows.Forms.Timer nie będzie działać, mimo że jest dostępny w zestawie narzędzi

Usługi: BackgroundWorkers nie będzie działać, gdy działa jako usługa. Zamiast tego użyj System.Threading.ThreadPools lub wywołań Async

scottweeden
źródło