Rozważ hipotetyczną metodę obiektu, który działa za Ciebie:
public class DoesStuff
{
BackgroundWorker _worker = new BackgroundWorker();
...
public void CancelDoingStuff()
{
_worker.CancelAsync();
//todo: Figure out a way to wait for BackgroundWorker to be cancelled.
}
}
Jak można czekać, aż BackgroundWorker zostanie ukończony?
W przeszłości ludzie próbowali:
while (_worker.IsBusy)
{
Sleep(100);
}
Ale to zakleszczenie , ponieważ IsBusy
nie jest czyszczone, dopóki nie RunWorkerCompleted
zostanie obsłużone zdarzenie, a to zdarzenie nie może zostać obsłużone, dopóki aplikacja nie przejdzie w stan bezczynności. Aplikacja nie będzie bezczynna, dopóki pracownik nie skończy. (Poza tym jest to zajęta pętla - obrzydliwa.)
Inni dodali sugestię umieszczenia go w:
while (_worker.IsBusy)
{
Application.DoEvents();
}
Problem polega na tym, że Application.DoEvents()
powoduje przetwarzanie komunikatów znajdujących się w kolejce, co powoduje problemy z ponownym wejściem (.NET nie jest ponownie wprowadzany).
Chciałbym użyć rozwiązania obejmującego obiekty synchronizacji zdarzeń, w których kod czeka na zdarzenie - ustawiane przez RunWorkerCompleted
programy obsługi zdarzeń pracownika . Coś jak:
Event _workerDoneEvent = new WaitHandle();
public void CancelDoingStuff()
{
_worker.CancelAsync();
_workerDoneEvent.WaitOne();
}
private void RunWorkerCompletedEventHandler(sender object, RunWorkerCompletedEventArgs e)
{
_workerDoneEvent.SetEvent();
}
Ale wracam do impasu: program obsługi zdarzeń nie może działać, dopóki aplikacja nie przejdzie w stan bezczynności, a aplikacja nie przejdzie w stan bezczynności, ponieważ czeka na zdarzenie.
Jak więc możesz czekać na zakończenie pracy w tle?
Aktualizacja Ludzie wydają się być zdezorientowani tym pytaniem. Wydaje się, że myślą, że będę używać BackgroundWorker jako:
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += MyWork;
worker.RunWorkerAsync();
WaitForWorkerToFinish(worker);
To jest nie to, że to nie to, co robię, a to nie to, co jest proszony jest tutaj. Gdyby tak było, nie byłoby sensu korzystać z pracownika w tle.
źródło
((BackgroundWorker)sender).CancellationPending
aby uzyskać odwołanie wydarzeniaWystąpił problem z tą odpowiedzią. Interfejs użytkownika musi nadal przetwarzać wiadomości podczas oczekiwania, w przeciwnym razie nie zostanie ponownie malowany, co będzie problemem, jeśli pracownik działający w tle potrzebuje dużo czasu, aby odpowiedzieć na żądanie anulowania.
Druga wada polega na tym,
_resetEvent.Set()
że nigdy nie zostanie wywołana, jeśli wątek roboczy zgłosi wyjątek - pozostawiając główny wątek w oczekiwaniu na czas nieokreślony - jednak tę wadę można łatwo naprawić za pomocą bloku try / final.Jednym ze sposobów jest wyświetlenie modalnego okna dialogowego z licznikiem czasu, który wielokrotnie sprawdza, czy pracownik w tle zakończył pracę (lub zakończył anulowanie w Twoim przypadku). Po zakończeniu pracy w tle modalne okno dialogowe przywraca sterowanie do aplikacji. Użytkownik nie może wchodzić w interakcje z interfejsem użytkownika, dopóki to nie nastąpi.
Inną metodą (zakładając, że masz otwarte maksymalnie jedno niemodalne okno) jest ustawienie ActiveForm.Enabled = false, a następnie pętla na Application, DoEvents, aż proces roboczy w tle zakończy anulowanie, po czym możesz ponownie ustawić ActiveForm.Enabled = true.
źródło
Prawie każdy z was jest zdezorientowany tym pytaniem i nie rozumie, w jaki sposób wykorzystuje się pracownika.
Rozważ obsługę zdarzeń RunWorkerComplete:
I wszystko dobrze.
Teraz pojawia się sytuacja, w której dzwoniący musi przerwać odliczanie, ponieważ musi wykonać awaryjne samozniszczenie rakiety.
Jest też sytuacja, w której musimy otworzyć bramki dostępu do rakiety, ale nie podczas odliczania:
Na koniec musimy zmniejszyć paliwo rakiety, ale nie jest to dozwolone podczas odliczania:
Bez możliwości oczekiwania na anulowanie przez pracownika, musimy przenieść wszystkie trzy metody do RunWorkerCompletedEvent:
Teraz mógłbym napisać swój kod w ten sposób, ale po prostu nie zamierzam. Nie obchodzi mnie to, po prostu nie.
źródło
Możesz sprawdzić RunWorkerCompletedEventArgs w RunWorkerCompletedEventHandler, aby zobaczyć, jaki był stan. Powodzenie, anulowanie lub błąd.
Aktualizacja : Aby sprawdzić, czy pracownik wywołał .CancelAsync (), używając tego:
źródło
Państwo nie czekać na pracownika tle, aby zakończyć. To w dużej mierze przeczy celowi uruchomienia osobnego wątku. Zamiast tego powinieneś pozwolić swojej metodzie zakończyć i przenieść kod, który zależy od zakończenia, w inne miejsce. Pozwalasz pracownikowi powiedzieć ci, kiedy to się skończy, i wywołać pozostały kod.
Jeśli chcesz poczekać, aż coś się zakończy, użyj innej konstrukcji wątkowej, która udostępnia WaitHandle.
źródło
Dlaczego nie możesz po prostu połączyć się ze zdarzeniem BackgroundWorker.RunWorkerCompleted. Jest to wywołanie zwrotne, które „Wystąpi, gdy operacja w tle zostanie zakończona, anulowana lub zgłosi wyjątek”.
źródło
Nie rozumiem, dlaczego chcesz czekać na zakończenie pracy w tle; naprawdę wydaje się być dokładnym przeciwieństwem motywacji dla klasy.
Jednak każdą metodę można rozpocząć od wywołania metody worker.IsBusy i nakazać jej zakończenie, jeśli jest uruchomiona.
źródło
Chcę tylko powiedzieć, że przyszedłem tutaj, ponieważ potrzebuję pracownika działającego w tle, aby poczekał, aż uruchomię proces asynchroniczny w pętli, moja poprawka była znacznie łatwiejsza niż wszystkie inne rzeczy ^^
Pomyślałem, że się tym podzielę, ponieważ właśnie w tym miejscu znalazłem rozwiązanie. Jest to również mój pierwszy post na temat przepełnienia stosu, więc jeśli jest zły lub cokolwiek, chciałbym krytyków! :)
źródło
Hm, może nie rozumiem dobrze twojego pytania.
Pracownik w tle wywołuje zdarzenie WorkerCompleted po zakończeniu jego „workermethod” (metoda / funkcja / podrzędna obsługująca zdarzenie backgroundworker.doWork ), więc nie ma potrzeby sprawdzania, czy BW nadal działa. Jeśli chcesz zatrzymać pracownika, sprawdź oczekującą nieruchomość w ramach swojej „metody pracowniczej”.
źródło
Przepływ pracy
BackgroundWorker
obiektu zasadniczo wymaga obsługiRunWorkerCompleted
zdarzenia zarówno w przypadku normalnego wykonywania, jak i przypadków użycia anulowania przez użytkownika. Dlatego istnieje właściwość RunWorkerCompletedEventArgs.Cancelled . Zasadniczo wykonanie tego poprawnie wymaga rozważenia metody Cancel jako metody asynchronicznej.Oto przykład:
Jeśli naprawdę nie chcesz, aby twoja metoda zakończyła się, sugerowałbym wstawienie flagi takiej jak
AutoResetEvent
na pochodnymBackgroundWorker
, a następnie przesłonięcie,OnRunWorkerCompleted
aby ustawić flagę. Jednak wciąż jest trochę niezdarnie; Zalecam traktowanie zdarzenia Cancel jako metody asynchronicznej i robienie wszystkiego, co aktualnie robi wRunWorkerCompleted
module obsługi.źródło
Jestem trochę spóźniony na imprezę tutaj (około 4 lata), ale co z konfiguracją wątku asynchronicznego, który może obsłużyć zajętą pętlę bez blokowania interfejsu użytkownika, a następnie wywołanie zwrotne z tego wątku będzie potwierdzeniem, że BackgroundWorker zakończył anulowanie ?
Coś takiego:
W istocie to, co robi, to odpalanie innego wątku, aby działał w tle, który po prostu czeka w swojej zajętej pętli, aby sprawdzić, czy
MyWorker
zakończyło się. PoMyWorker
zakończeniu anulowania wątek zostanie zakończony i możemy go użyćAsyncCallback
do wykonania dowolnej metody, której potrzebujemy, aby po pomyślnym anulowaniu - będzie działać jak pseudo-zdarzenie. Ponieważ jest to niezależne od wątku interfejsu użytkownika, nie blokuje interfejsu użytkownika, gdy czekamy naMyWorker
zakończenie anulowania. Jeśli naprawdę chcesz zablokować i czekać na anulowanie, jest to dla ciebie bezużyteczne, ale jeśli chcesz tylko poczekać, aby móc rozpocząć kolejny proces, to działa dobrze.źródło
Wiem, że jest to naprawdę późno (5 lat), ale to, czego szukasz, to użyć wątku i SynchronizationContext . Będziesz musiał kierować wywołania interfejsu użytkownika z powrotem do wątku interfejsu użytkownika „ręcznie”, zamiast pozwolić, aby Framework robił to automatycznie.
Pozwala to na użycie wątku, na który możesz poczekać, jeśli zajdzie taka potrzeba.
źródło
źródło
Rozwiązanie Fredrika Kalsetha na ten problem jest najlepsze, jakie do tej pory znalazłem. Inne rozwiązania,
Application.DoEvent()
które mogą powodować problemy lub po prostu nie działają. Pozwólcie, że wrzucę jego rozwiązanie do klasy wielokrotnego użytku. PonieważBackgroundWorker
nie jest zapieczętowany, możemy wyprowadzić z niego naszą klasę:Dzięki flagom i odpowiedniemu blokowaniu upewniamy się, że
_resetEvent.WaitOne()
naprawdę zostanie wywołany tylko wtedy, gdy jakaś praca została rozpoczęta, w przeciwnym razie_resetEvent.Set();
może nigdy nie zostać wywołana!Próba ostatecznie zapewnia, że
_resetEvent.Set();
zostanie wywołana, nawet jeśli w naszej obsłudze DoWork powinien wystąpić wyjątek. W przeciwnym razie aplikacja może zawiesić się na zawsze podczas wywoływaniaCancelSync
!Używalibyśmy tego w ten sposób:
Możesz również dodać procedurę obsługi do
RunWorkerCompleted
zdarzenia, jak pokazano tutaj:BackgroundWorker Class (dokumentacja Microsoft) .
źródło
Zamknięcie formularza powoduje zamknięcie mojego otwartego pliku dziennika. Pracownik działający w tle zapisuje ten plik dziennika, więc nie mogę
MainWin_FormClosing()
zakończyć, dopóki nie zakończy się proces roboczy w tle. Jeśli nie czekam na zakończenie pracy mojego pracownika działającego w tle, zdarzają się wyjątki.Dlaczego to takie trudne?
Prosty
Thread.Sleep(1500)
działa, ale opóźnia zamknięcie (jeśli jest zbyt długi) lub powoduje wyjątki (jeśli jest zbyt krótki).Aby zamknąć zaraz po zakończeniu procesu roboczego w tle, wystarczy użyć zmiennej. To działa dla mnie:
źródło
Możesz wycofać się ze zdarzenia RunWorkerCompleted. Nawet jeśli dodałeś już moduł obsługi zdarzeń dla _worker, możesz dodać kolejny i będą one wykonywane w kolejności, w jakiej zostały dodane.
może to być przydatne, jeśli masz wiele powodów, dla których może wystąpić anulowanie, co sprawia, że logika pojedynczego programu obsługi RunWorkerCompleted jest bardziej skomplikowana niż chcesz. Na przykład anulowanie, gdy użytkownik próbuje zamknąć formularz:
źródło
Stosuję
async
metodę iawait
czekam, aż pracownik zakończy pracę:i w
DoWork
metodzie:Można też hermetyzacji
while
pętlę wDoWork
ztry ... catch
na zestaw_isBusy
jestfalse
na wyjątku. Lub po prostu sprawdź_worker.IsBusy
wStopAsync
pętli while.Oto przykład pełnej realizacji:
Aby zatrzymać pracownika i poczekać, aż dobiegnie do końca:
Problemy z tą metodą to:
źródło
o rany, niektóre z nich stały się absurdalnie złożone. wszystko, co musisz zrobić, to sprawdzić właściwość BackgroundWorker.CancellationPending wewnątrz procedury obsługi DoWork. w każdej chwili możesz to sprawdzić. gdy jest w toku, ustaw e.Cancel = True i zwolnij z metody.
// metoda tutaj private void Worker_DoWork (nadawca obiektu, DoWorkEventArgs e) {BackgroundWorker bw = (nadawca jako BackgroundWorker);
}
źródło