BackgroundWorker - problem z wątkiem przy dwóch funkcjach


(Musialowski Tomasz) #1

Już nie po raz pierwszy przyszło mi stanąć przed utrudnieniami jakie niesie ze sobą programowanie z wykorzystaniem wątków w tle. Problem mój polega na tym, że tworzę obiekt typu BackgroundWorker o nazwie bgProcess. Mam również dwie funkcje, jedna sprawdza poprawność danych w tle, druga natomiast przesyła je do bazy danych - również w tle. Zarówno jedna jak i druga funkcja odpalana jest za pomocą swojego przycisku [Zarejestruj] oraz [sprawdź]. Teraz zaczynają się schody. Z rozwiązania, które znalazłem w internecie musiałem użyć konstrukcji:

bgProcess.DoWork += delegate(object sender, DoWorkEventArg e) { funkcja... };

Zrobiłem więc takie wywołanie zarówno w przypadku wysłania danych jak i ich sprawdzenia tzn. z grubsza coś takiego:

private void zarejestruj_Click(object sender, RoutedEventArgs e) {

   bgProcess.DoWork += delegate(object s, DoWorkEventArg arg) { funkcja... };

   bgProcess.ProgressChanged += delegate(object s, ProgressChangedEventArg arg) { funkcja... };

   bgProcess.RunWorkedCompleted += delegate(object s, RunWorkedCompletedEventArg arg) { funckja... };

   if (!bgProcess.IsBusy)

                bgProcess.RunWorkerAsync(dane);

}

Niestety dzieje się coś dziwnego. Np. uruchamiam wysyłanie danych, wyskakuje komunikat informujący, że dane zostały przesłane bądź nie (który jest właściwy tylko tej funkcji), w RunWorkerCompleted odblokowuję przyciski więc wiem, że funkcja w tle skończyła się wykonywać. Odpalam więc sprawdzanie a tu niespodzianka, komunikat o stanie przesyłania danych z funkcji zarejestruj, zupełnie jakby funkcje DoWork dodały się do siebie. Podobnie z ProgressChanged - ponieważ zarejestruj przesyła do niej dane typu string a sprawdzanie typu boolean to powstaje galimatias przy ich konwertowaniu i odczytaniu jakby stało się zupełnie to samo co z DoWork. Spróbowałem więc inaczej:

private void sprawdz_Click(object sender, RoutedEventArgs e)

        {

            zarejestruj.IsEnabled = false;

            sprawdz.IsEnabled = false;

            Object[] dane = new Object[5];

            dane[0] = login.Text;

            dane[1] = password.Password;

            dane[2] = repassword.Password;

            dane[3] = email.Text;

            dane[4] = reemail.Text;

            bgProcess.DoWork -= registerUser_DoWork;

            bgProcess.ProgressChanged -= registerUser_ProgressChanged;

            bgProcess.DoWork += checkForm_DoWork;

            bgProcess.ProgressChanged += checkForm_ProgressChanged;

            bgProcess.RunWorkerCompleted += backgroundComplete;

            if (!bgProcess.IsBusy)

                bgProcess.RunWorkerAsync(dane);

        }

private void zarejestruj_Click(object sender, RoutedEventArgs e)

        {

            zarejestruj.IsEnabled = false;

            sprawdz.IsEnabled = false;

            Object[] dane = new Object[5];

            dane[0] = login.Text;

            dane[1] = password.Password;

            dane[2] = repassword.Password;

            dane[3] = email.Text;

            dane[4] = reemail.Text;

            bgProcess.DoWork -= checkForm_DoWork;

            bgProcess.ProgressChanged -= checkForm_ProgressChanged;

            bgProcess.DoWork += registerUser_DoWork;

            bgProcess.ProgressChanged += registerUser_ProgressChanged;

            bgProcess.RunWorkerCompleted += backgroundComplete;

            if (!bgProcess.IsBusy)

                bgProcess.RunWorkerAsync(dane);

        }

Zdefiniowałem funkcje osobno i potem próbowałem w metodzie sprawdzania wyrejestrować metody wysyłania danych, a w metodzie wysyłania danych, te funkcje które zostały zdefiniowane w rejestrującej. Niestety nie działa to w zamierzony przeze mnie sposób. backgroundComplete() odblokowuje kontrolki zarejestruj i sprawdz, nie ma więc problemu z wielowątkowością. Jak zarobić bym mógł swobodnie odpalać sobie na zmianę w tle obie te funkcje?


(Tomek Matz) #2

Zgadzam się z tym, że wielowątkowość może być problematyczna, ale w Twoim przypadku nie ma miejsca na jakiekolwiek problemy. Błąd, który masz pewno wynika z tego, że nie usuwasz delegatu, który ma być wywołany w sytuacji wystąpienia zdarzenia RunWorkerCompleted. W efekcie po kilkukrotnym kliknięciu zarejestruj i sprawdź masz całą masę funkcji, które będą wywołane w sytuacji wystąpienia tego zdarzenia (do danego zdarzenia można przypisać dowolną ilość delegatów), przy czym jedne będą odnosić się dla akcji zarejestruj, a drugie dla akcji sprawdź.

BTW taki mały tip z mojej strony .... na form-ie możesz mieć kilka backgroundworker-ów. I jeszcze jeden tip ... zawsze możesz zerknąć do kodu zawartego w pliku Designer.cs Twojej formy, aby zobaczyć jak można przypisywać w kodzie delegaty do zdarzenia (choć tak na prawdę dzieje się to automatycznie, gdy wpiszesz += i naciśniesz tab).


(Musialowski Tomasz) #3

Dodałem usunięcie delegatów danej funkcji do jej RunWorkerCompleted łącznie z nią samą. Obawiałem się, że nie będzie tego można zrobić podczas jej wywołania, a tu jednak :slight_smile:

private void checkForm_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)

        {

            bgProcess.DoWork -= checkForm_DoWork;

            bgProcess.ProgressChanged -= checkForm_ProgressChanged;

            bgProcess.RunWorkerCompleted -= checkForm_RunWorkerCompleted;

            zarejestruj.IsEnabled = true;

            sprawdz.IsEnabled = true;

        }

Dzięki matzu, już nie pierwszy raz mi pomagasz, masz u mnie duże piwo.

Jeszcze co do tych kilku backgroundworkerów to robiłem już coś podobnego i miałem problem z multi threadem mimo, że oba obiekty backgroundworkera nie odwoływały się nigdzie do jakiegoś wspólnego obiektu z głównego wątku. Ale to początki mojej przygody z C# i WPF więc możliwe, że coś po prostu sknociłem :slight_smile: Jeszcze raz dzięki.


(Tomek Matz) #4

Szczerze mówiąc sam nie wiedziałem, że tak się da. Jakbyś jednak napotkał jakieś problemy to zawsze możesz to przenieść do tych procedur obsługujących zdarzenie Click i tam na pewno żadnych problemów nie będzie.

Spróbuj jeszcze raz, bo nie powinno tu być żadnych problemów (pojawiłyby się dopiero wtedy, gdy odpaliłbyś dwa na raz).

Poza tym nie ma za co :slight_smile: