Adrian Botor

Adrian Botor Architekt
oprogramowania

Temat: Użycie ProgressBar przy ładowaniu Form

Witam,

Chciałbym podczas ładowania Form'ów wyświetlać osobne okno z ProgressBar'em.

Mój problem polega na tym, że takie okno potrafię wyświetlić ale natomiast progressBar nie rusza się (mam na myśli Value = 100 oraz Style = Marquee).


Obrazek


Obrazek


Uploaded with ImageShack.us

Natomiast kod, który to uruchamia jest następujący.

private void ListaTowarow_Load(object sender, EventArgs e)
{
Loading loading = new Loading();
loading.Show();
loading.Update(); Bez tej metody w ogóle obiekty na formie by się nie pokazały.

// Załadowanie danych
LoadData(); Ta metoda bardzo długo się wykonuje (3-4 sec)

loading.Close();
}

Proszę o podpowiedz jak to można wykonać sprawnie.
Próbowałem już z backgroundworkerem ale nie ładował kontrolek. Osobny wątek też nie pomagał. Własność ProgressBar.InvokeRequired jest ustawiona na false pomimo, że zmieniam Value w kontruktorze From'a LoadingAdrian Botor edytował(a) ten post dnia 18.01.12 o godzinie 13:09
Jan Kowalski

Jan Kowalski Specjalista

Temat: Użycie ProgressBar przy ładowaniu Form

To sie dzieje dlatego, ze wszystko jest wykonywane w jednym watku i okno nie bedzie przetwarzalo zadnych komunikatow, dopoki .NET w srodku nie wykona GetMessage().

GetMessage nie wykona sie, dlatego ze musi sie skonczyc obecna iteracja petli eventowej ktora jest kodem jednowatkowym. To oznacza ze nie tylko nie bedzie widac jak sie rusza progress bar (WM_PAINT), ale tez to, ze z oknem nic nie zrobisz. Aplikacja bedzie sie wydawala dla uzytkownika jako "zawieszona".

Rozwiazaniem tego problemu jest podzielenie tej petli na dwa watki: jeden ktory dalej bedzie nasluchiwal na komunikaty z systemu operacyjnego i je przetwarzal. Drugi bedzie ladowal formularz i wykonywal dlugotrwala prace.

TL;DR:

private void ListaTowarow_Load(object sender, EventArgs e)
{
Loading loading = new Loading();
loading.Show();

new Task( () => LoadData()).ContinueWith(() => loading.Close()).Start();
}


Mozesz miec problemy z dostepem do UI z innego watku niz ten w ktorym zostal utworzony (system operacyjny stosuje to jako mechanizm obronny przed deadlockiem w przypadku kombinacji GetMessage() i PostMessage() sa wywolywane na innych watkach i kazde z nich by czekalo na drugie az sie wykona a wykonanie PostMessage() jest zalezne od pomyslnego wykonania GetMessage() w Windows Message Loop.)

TL;DR: Jak to co wyzej Ci nie zadziala to:

private void ListaTowarow_Load(object sender, EventArgs e)
{
Loading loading = new Loading();
loading.Show();

new Task( () => LoadData()).ContinueWith(() => loading.BeginInvoke(() => loading.Close()).Start();
}
Jan Kowalski edytował(a) ten post dnia 18.01.12 o godzinie 15:06
Adrian Botor

Adrian Botor Architekt
oprogramowania

Temat: Użycie ProgressBar przy ładowaniu Form

Dla obydwu przypadków pojawia się komunikat

"Delegate 'System.Action<System.Threading.Tasks.Task>' does not take 0 arguments"
Jan Kowalski

Jan Kowalski Specjalista

Temat: Użycie ProgressBar przy ładowaniu Form

.Jan Kowalski edytował(a) ten post dnia 18.01.12 o godzinie 17:45
Adrian Botor

Adrian Botor Architekt
oprogramowania

Temat: Użycie ProgressBar przy ładowaniu Form

W pierwszym przypadku błąd w postawić

W drugim jak starałem się uruchomić to też


new Task(() => LoadData()).ContinueWith((t) => f2.BeginInvoke((MethodInvoker)delegate
{
f2.Close();
})).Start();


"Metody Start nie można wywołać dla zadania kontynuacji."

-------------------------------------------

Ale zadziała tak i wszystko byłoby ładnie gdyby zdanie dalej poszło.

            
Form2 f2 = new Form2();
f2.Show();

Task task = new Task(() => LoadData());
task.ContinueWith((t) => f2.BeginInvoke((MethodInvoker)delegate
{
f2.Close();
}));
task.Start();


Bez dalegata pisze o cross-thread calls

Tak mi się zawiesza LoadData();Adrian Botor edytował(a) ten post dnia 18.01.12 o godzinie 17:16
Sławomir Orłowski

Sławomir Orłowski PhD, physicist,
software
developer/architect
team leader...

Temat: Użycie ProgressBar przy ładowaniu Form

Napisz, gdzie używasz metody ProgressBar.PerformStep()?
Zrób wątek, który będzie osobno używał metody LoadData().
W tej metodzie w jej kluczowych momentach zrób zdarzenie typu OnProgressChanged.
Podepnij się do tego zdarzenie z formy pokazującej progressBar i odpowiednio uaktualniaj kontrolkę.
Trzeba też pewnie się zabezpieczyć (Invoke lub BeginInvoke) przed tzw. cross-thread operation na kontrolkach formy, ale to jest opisane w każdym tutorialu do WindowsForms.
Adrian Botor

Adrian Botor Architekt
oprogramowania

Temat: Użycie ProgressBar przy ładowaniu Form

Nie używam metody ProgressBar.PerformStep()?

Chciałem prosto utworzyć ProgressBar. Nadać wartości Value i Style = Marquee abyś zasymulować postęp pracy.

W LoadData ładuję dane wg paramatrów do DataGridView przez własność DataSource.
Sławomir Orłowski

Sławomir Orłowski PhD, physicist,
software
developer/architect
team leader...

Temat: Użycie ProgressBar przy ładowaniu Form

Adrian Botor:
Nie używam metody ProgressBar.PerformStep()?
A jak uaktualniasz wskazania ProgressBar? Metodą Increment?

Chciałem prosto utworzyć ProgressBar. Nadać wartości Value i Style = Marquee abyś zasymulować postęp pracy.
Aby symulować postęp prac, należy w kluczowych miejscach długotrwałego procesu uaktualniać progresBar. Wydaje mi się wygodne, aby informować o tym poprzez zdarzenia z metody LoadData(). Możesz wtedy podpiąć się z metodą/metodami zdarzeniowymi i robić co tylko chcesz.
Czyli:
Tworzysz sobie powiedzmy event OnProgressChange(object sender, {tutaj jakiś obiekt (np. enum) mówiący o aktualnie wykonywanej akcji})
I teraz:
LoadData()
{
....jakaś akcja
OnProgressChange(...) <-generacja zdarzenia
....jakaś akcja
OnProgressChange(...) <-generacja zdarzenia
....jakaś akcja
OnProgressChange(...) <-generacja zdarzenia
... i tak kilka, kilkanaście razy.
}
Z innej klasy podpinasz się wtedy ze zdarzeniem.
Metoda LoadData(), tak jak wcześniej napisał kolega Jan, powinna być w osobnym wątku, żeby nie blokować wątku głównego okna.
>
W LoadData ładuję dane wg paramatrów do DataGridView przez własność DataSource.Sławomir Orłowski edytował(a) ten post dnia 18.01.12 o godzinie 17:38
Adrian Botor

Adrian Botor Architekt
oprogramowania

Temat: Użycie ProgressBar przy ładowaniu Form

Oczywiście mógłbym tak zrobić i wiem o czym mowa.

Dziękuję za odpowiedzi. Uzyskałem już działający rezultat.


private void ListaTowarow_Load(object sender, EventArgs e)
{
Loading loading = new Loading();
loading.Show();

Task task = new Task(() => LoadData());
task.ContinueWith((t) =>
{
Invoke((MethodInvoker)delegate
{
loading.Close();
});
});
task.Start();
}


Uproszczony progressBar

//
// progressBar1
//
this.progressBar1.Location = new System.Drawing.Point(12, 44);
this.progressBar1.Name = "progressBar1";
this.progressBar1.Size = new System.Drawing.Size(300, 23);
this.progressBar1.Style = System.Windows.Forms.ProgressBarStyle.Marquee;
this.progressBar1.TabIndex = 1;


W metodzie LoadData użyłem kilka razy Invoke aby załadować dane do kontrolek.

Przypuszczam, że podobny efekt uzyskałbym używająć Thread'ów lub BackgroudWorker'a dla metody LoadData()?Adrian Botor edytował(a) ten post dnia 18.01.12 o godzinie 17:47
Jan Kowalski

Jan Kowalski Specjalista

Temat: Użycie ProgressBar przy ładowaniu Form

Adrian Botor:
Dziękuję za odpowiedzi. Uzyskałem już działający rezultat.
W metodzie LoadData użyłem kilka razy Invoke aby załadować dane do kontrolek.
>

W tym konkretnym przypadku uzyskałeś efekt i możesz przejść do pracy nad kolejnym elementem systemu. Widzę jednak, że stawiasz swoje pierwsze kroki w .NET i wielowątkowości to na przyszłość może Ci się przyda i unikniesz kodu "pisanego na kolanie" :-)

To co teraz zrobiłeś "użyłem kilka razy Invoke aby załadować dane do kontrolek" kompletnie kłóci się z ideą wielowątkowości. W wielowątkowości chodzi przede wszystkim o zminimalizowanie komunikacji i zależności pomiędzy poszczególnymi wątkami, tak aby cały czas mieć procesor zajęty czymś interesującym.

Metoda Invoke, zawiesza działanie wątku wykonującego obliczenia, ustawiając jego stan na oczekujący, , robi context-switch, uaktywnia wątek z którego zostało utworzone UI i który jest w stanie Alertable i wywołuje mechanizmem APC (kombinacja SleepEx + QueueUserAPC) kod na wątku UI, który jak skończy działanie, znowu usypia UI w stanie alertable, context switch i sygnalizuje pierwszy wątek, a ten dalej oblicza.

Za dużo czasu marnujesz w tym przypadku na niepotrzebną synchronizację pomiędzy tymi wątkami i tracisz to co najważniejsze w wielowątkowości - wzrost przepustowości obliczeń.

Na małych ilościach danych możesz nie zauważyć tego gołym okiem, ale na ciut większych to taki sposób działania LoadData będzie działać wolniej niż jego jednowątkowa odmiana.

Najlepszym sposobem rozważania wielowątkowych obliczeń, jest traktowanie każdego wątku jako osobny proces, działający na osobnej maszynie, a te maszyny są połączone przez internet przez modem 56k. Wtedy łatwiej można sobie wyobrazić koszty komunikacji pomiędzy wątkami.

W tym przypadku, lepszym rozwiązaniem by było utworzyć parę zmiennych tymczasowych, które by przechowywały te dane które miałyby znaleźć się docelowo w kontrolkach. Czy to proste inty, stringi, czy nawet wielkie DataSet-y i dopiero pod koniec, jak już wszystko załadujesz do tych zmiennych to przekaż je do "ContinueWith" i tam je załaduj do UI. Unikniesz niepotrzebnej synchronizacji tych wątków.
Przypuszczam, że podobny efekt uzyskałbym używająć Thread'ów lub BackgroudWorker'a dla metody LoadData()?

Tak, TPL i PLINQ razem są frameworkiem który wprowadza abstrakcje wątków i umożliwia rozważanie problemów pod kątem technik: Task Parallelism i Data/Dataset parallelism - wzorowane na bibliotece TBB Intel'a.
Marcin S.

Marcin S. Programista, trener
i konsultant w
zakresie .NET/.NET
Cor...

Temat: Użycie ProgressBar przy ładowaniu Form

Proponuję użyć BackgroudWorkera. Najłatwiejszy w użyciu i unikniesz problemów z wątkami.
Jan Kowalski

Jan Kowalski Specjalista

Temat: Użycie ProgressBar przy ładowaniu Form

Marcin Sulecki:
Proponuję użyć BackgroudWorkera. Najłatwiejszy w użyciu i unikniesz problemów z wątkami.

Nie prawda.

BackgroundWorker wykona operacje na innym wątku. Z tą różnicą, że wyjątki rzucone przez runtime WindowsForms nie trafią do użytkownika tej klasy (programisty) bezpośrednio o ile sam własnoręcznie nie subskrybuje do RunWorkerCompleted i nie sprawdzi stanu właściwości o nazwie "Error".

W takim wypadku, aplikacja wykonując operacje jakie były tutaj z użyciem Control.Invoke, w połowie działania przerwie operacje przy pierwszym wątku rzuconym przy próbie zmiany UI i sprawi iluzję tego, że wykonuję się poprawnie, a w rzeczywistości stan aplikacji będzie nadawał się tylko do złapania (catch) ;-) - w skróci: rezygnujemy z poprawności działania programu na rzecz "uniknięcia problemu z wątkami".
Adrian Botor

Adrian Botor Architekt
oprogramowania

Temat: Użycie ProgressBar przy ładowaniu Form

Po sprawdzeniu BackgroundWorker'a. Przy użyciu też trzeba użyczać czasami Invoke aby nie wpaść w "cross-thread calls" tylko już nie wiem jak się tam wątki przełączają.
Jan Kowalski

Jan Kowalski Specjalista

Temat: Użycie ProgressBar przy ładowaniu Form

Nie ma tutaj czarów.

BackgroundWorker to nic innego jak wrapper do ThreadPool.QueueUserObject(), ktory wywoluje przekazana delegate w klauzuli try/catch i w catch zamiast rzucic dalej, to zachowuje Exception w RunAsyncWorker.Error a w finally odpala event RunAsyncWorkerCompleted.

konto usunięte

Temat: Użycie ProgressBar przy ładowaniu Form

Cóż... w paru moich programach Użycie BackgroundWorker'a uratowało mi tyłek (i to bardzo - zwłaszcza przy deadline'ach). Oczywiście można używać go jako kontrolkę, jednak osobiście preferuję opcję stworzenia Klasy potomnej względem klasy BackgroundWorker i nadpisanie metod OnDoWork i OnProgressChanged (oczywiście również metody - OnWorkerCompleted). "Komunikację" pomiędzy nimi bardzo łatwo wykonać przy użyciu metody ReportProgress, która "wyśle" informację do OnProgressChanged.
Dariusz Bujak

Dariusz Bujak Student, młodszy
programista JAVA,
C++

Temat: Użycie ProgressBar przy ładowaniu Form

Witam

Trochę odświeżę temat, a mianowicie mam taką sytuacje że uruchamiam wątek który szuka po bazie jakiś danych i wyświetla to do DataGridView z tym że właśnie do uzupełniania tabeli używam metody Invoke co niestety skutkuje w przypadku wypisywaniem dużej ilości danych "mruganiem" formatek i użytkownik nie ma dostępu do przycisków.

W jaki sposób można rozwiązać ten problem?

Czy można inaczej dodawać elementy?

Czy sposób opisany przez Jan Kowalski nie skutkował by tym samym?
Marcin Pasternak

Marcin Pasternak Programista .NET, C#

Temat: Użycie ProgressBar przy ładowaniu Form

Jeśli jest to faktycznie duża ilość danych to lepiej pokusić się o podzielenie wyświetlanych danych na strony(page-owanie).
Możesz pokusić się o DataVirtualization (wyciągasz tylko te dane które są aktualnie widoczne dla użytkownika) - de facto page-owanie to w sumie DataVirtualization.
Dariusz Bujak

Dariusz Bujak Student, młodszy
programista JAVA,
C++

Temat: Użycie ProgressBar przy ładowaniu Form

Ok, dzieki myślałem że można to jakoś żeby DataGridView stworzyć w tym wątku i na takiej zasadzie.

Natomiast mam inny problem a mianowicie w odrębnym wątku nasłuchuje port COM i jak uda mi sie sczytać z czytnika kodów kreskowych to chiałbym aby pokazało mi sie okienko. I wszystko fajnie tylko żeby pokazać okno używam ShowDialog i tak to chce zostawić ponieważ nie chce aby ktos cos innego wtedy robił, jednak że takie wywołanie blokuje mi wątek i jeżeli jest to okno widoczne a ktoś jeszcze raz zbliży kod do czytnika to po zamknięciu tegoż okna pojawia mi sie następne bo wątek zaczyna pracować odnowa. Próbowałem użyć delegate i Invoke głównej formatki ale nie pomogło.

Jak mógłbym rozwiązać ten problem z blokowaniem wątku czytającego?

Następna dyskusja:

jak wstawic VCL Form do pro...




Wyślij zaproszenie do