Laden...

Thread oder Backgroundworker verwenden, um blockieren des GUIs zu verhindern?

Erstellt von sbirg vor 14 Jahren Letzter Beitrag vor 14 Jahren 1.609 Views
S
sbirg Themenstarter:in
17 Beiträge seit 2009
vor 14 Jahren
Thread oder Backgroundworker verwenden, um blockieren des GUIs zu verhindern?

Den Freitag Nachmittag, inzwischen schon späten Abend habe ich genutzt um mich mal etwas genauer mit Asynchronen Abläufen zu beschäftigen. Hintergrund ist auch ein aktueller Prozess meiner Anwendung, den ich optimieren möchte. Jetzt bin ich über die Thread Klasse gestolpert, und auch BackgroundWorker, und um so mehr ich lese, desto unsicher werde ich mir wie ich was am besten anwenden soll. Daher brauche ich an dieser Stelle eure Hilfe.

Vereinfachte Darstellung meiner Anwendung, die im Moment syncron abläuft:

  1. Download Datei1
  2. Fortschrittbalken aktualisieren
  3. Verarbeitung Datei1
  4. Fortschrittbalken aktualisieren
  5. Download Datei2
  6. Fortschrittbalken aktualisieren
  7. Verarbeitung Datei2
  8. Fortschrittbalken aktualisieren
  9. Download Datei3
    ...

Das ganze Anwendung läuft innerhalb eines Windows Form ab. Mein Hauptproblem ist, dass die Anwendung wären des ganzen Vorgang nicht reagiert. Das sieht dann so aus als ob es abgestützt ist, was allerdings nicht der Fall ist. Auf der Suche nach einer Lösung für dieses Problem bin ich dann beim Thema Asynchronen Abläufen angekommen. Jetzt möchte ich natürlich den gesamten Ablauf optimieren, nach guten "Programmierstil", und natürlich auch das Wie und Warum verstehen.

Nachdem was ich gelesen habe kam mir jetzt folgende Idee:
In dem Windows Form wird der Download gestartet -> ThreadDownload. Ist der Download einer Datei fertig, wird Download der nächsten Datei im ThreadDownload gestartet. Gleichzeit wird die Download Datei in einem ThreadVerarbeitung verarbeitet (starte ich den Thread aus dem Windows Form, oder aus dem ThreadDownload?), usw. bis alles fertig ist. Ist ein Download oder Verarbeitung fertig, wird ein Ereignis erzeugt, welches den Fortschrittbalken aktualisiert.

Löst man das so? Folgende Fragen ist dann bei mir noch offen: Benutzt man hier anstatt die Klasse Thread einen BackgroundWorker? Ganz einleuchtet ist mir noch nicht, wann Thread und wann BackgroundWorker besser ist.

Hoffe auf eure Hilfe.

Lg Birgit

49.485 Beiträge seit 2005
vor 14 Jahren

Hallo sbirg,

siehe [FAQ] Warum blockiert mein GUI?

Ob du Thread oder BackgroundWorker nimmst, ist im Wesentlichen Geschmackssache. Thread ist einen Hauch besser, wenn man den Code von Hand schreibt und BackgroundWorker einen Hauch besser, wenn man den Designer benutzt.

herbivore

C
282 Beiträge seit 2008
vor 14 Jahren

Der BackgroundWorker hat eine Funktionalität, die den prozentualen Status auswerten kann. Das Ganze per Event. Wenn man beispielsweise einen ProgressBar nutzt ist das Ding dafür sehr gut geeignet.

5.742 Beiträge seit 2007
vor 14 Jahren

Hallo sbirg,

Benutzt man hier anstatt die Klasse Thread einen BackgroundWorker? Ganz einleuchtet ist mir noch nicht, wann Thread und wann BackgroundWorker besser ist.

Ich persönlich würde hierfür ThreadPool.QueueUserWorkItem verwenden.

Einen Thread per new Thread() zu erzeugen und zu starten ist da deutlich resourcenhungriger.

Da du vermutlich auch mehrere Dateien parallel dowloaden möchtest, wäre ein BGW weniger geeignet - und wenn man die Dinger manuell erzeugen muss, geht das bisschen Komfort, das sie bieten, IMHO schnell flöten.

115 Beiträge seit 2008
vor 14 Jahren

Hallo sbirg,

genau damit hab ich mich in der letzten Zeit auch beschäftigt. Neben den Turotials hier findest Du auch beim Codeproject eine ziemlich umfrangreiche Einleitung zu Threads und co... Thread-Tutorial auf CodeProject

Ich hab das bei mir wie folgt gelöst. In meinem Form wähle ich Dateien aus die verarbeitet werden sollen. Die Verarbeitung beginnt nach einem Klick auf "Start":

(Der Code funktioniert so natürlich nicht, da ich fast alles rausgeschmissen hab, was fürs Threading unwesentlich ist)


private void StartButton_Click(object sender, EventArgs e)
{
    progressBarMarquee.Style = ProgressBarStyle.Marquee;
    progressBarMarquee.MarqueeAnimationSpeed = 1;

    progressBarFiles.Style = ProgressBarStyle.Continuous;
    progressBarFiles.Minimum = 0;
    progressBarFiles.Maximum = completeSize;
    progressBarFiles.Value = 0;

    //Parametriesierter Start eines Threads
    //StartReading ist die Methode die im neuen Thread ausgeführt wird,
    //dateien ist das Objekt, dass an die Methode übergeben wird
    new Thread(StartReading).Start(dateien); 
}

...

// Methode, die im Thread ausgeführt wird.
private void StartReading(object container)
{
    // Ich hab hier jeweils Quell- und Zieldatei in einem Dictionary gespeichert.
    Dictionary<string, string> temp = new Dictionary<string, string>();

    temp = (Dictionary<string, string>)container;

    foreach (KeyValuePair<string, string> pair in temp)
    {
        try
        {
            string ziel = pair.Value;
            string quelle = pair.Key;

            do
            {
                // Wenn die Anwendung während der Bearbeitung des Threads gschlossen
                // wird, verlasse ich mit RETURN den Thread
                if (!fStopThread)
                {
                    //macht was mit den Dateien
                    String line;
                    line = sr.ReadLine();
                }
                else
                {
                    return;
                }
            }
            while (!sr.EndOfStream);

            // Hier wird die ProgressBar in Deiner MainForm aktualiesiert
            //Invoke deshalb, weil direkte Zugriffe auf Teile der Form von einem anderen
            //Thread aus nicht zulässig sind
            // ChangeProgressFiles ist dabei die Methode, die die Änderung durchführt
            this.Invoke(new MethodInvoker(ChangeProgressFiles));
        }
    }
}

private void ChangeProgressFiles()
{
     progressBarFiles.Value += Convert.ToInt32(countSize);
}

--Edit--
Was ich leider noch nicht rausgefunden habe ist, wie ich benannte Threads mit Parametern starten kann. Das würde das kontrollierte beenden der Anwendung deutlich sicherer machen. Ob das nämlich mit meinem boolschen Wer so funktioniert, kann ich nicht sagen.
Vor der Einfügung ging das Beenden mal mit, mal ohne Exception.

Grüße,
der Michael

5.742 Beiträge seit 2007
vor 14 Jahren
  
//Parametriesierter Start eines Threads  
//StartReading ist die Methode die im neuen Thread ausgeführt wird,  
//dateien ist das Objekt, dass an die Methode übergeben wird  
new Thread(StartReading).Start(dateien);  

Wie gesagt: Lieber den ThreadPool verwenden!

115 Beiträge seit 2008
vor 14 Jahren
  
//Parametriesierter Start eines Threads  
//StartReading ist die Methode die im neuen Thread ausgeführt wird,  
//dateien ist das Objekt, dass an die Methode übergeben wird  
new Thread(StartReading).Start(dateien);  

Wie gesagt: Lieber den ThreadPool verwenden!

Das mit dem Parametrisierten Thread gefällt mir auch nicht so. Siehe mein Edit weiter oben

Grüße,
der Michael

365 Beiträge seit 2004
vor 14 Jahren

Das mit dem Parametrisierten Thread gefällt mir auch nicht so. Siehe mein Edit weiter oben

Der Hinweis auf den ThreadPool hat weniger mit dem Parametrisieren zu tun, sondern vielmehr damit, dass dies ressourcenschonender ist. Es ist nicht unbedingt immer sinnvoll einen eigenen Thread zu erstellen. Je mehr Threads desto mehr CPU Zyklen gehen für das Umschalten zwischen den einzelnen Threads drauf. Stattdessen kannst du es dem ThreadPool überlassen, die Arbeit auf die bereits bestehenden Threads aufzuteilen.

S
sbirg Themenstarter:in
17 Beiträge seit 2009
vor 14 Jahren

Danke an alle für die tolle Resonanz. Ich habe inzwischen etwas mit den Threads gespielt und ich denke ich verstehe das ganz nun auch etwas besser.

Zu euren Antworten habe ich auch noch ein paar Fragen:

Da du vermutlich auch mehrere Dateien parallel dowloaden möchtest

Das macht nur Sinn, wenn man unterschiedliche Downloadquellen hat, oder? Wenn ich nur von einer Quelle Dateien lade, wäre dies sogar kontraproduktiv, da ich die Dateien sofort bearbeiten möchte, sobald sie heruntergeladen ist. Wenn ich mehrere Dateien gleichzeit herunterlade, müssen sich diese ja die Bandbreite teilen, sprich bis ich eine Datei verarbeiten kann, würde in diesem Fall länger dauern. Das sehe ich doch richtig?

Nächste Frage: Es ist überall zu lesen, dass man von dem einen Thread nicht auf Daten eines anderen Thread zugreifen soll. Ich kann jetzt beim Start einen Parameter übergeben oder zur Laufzeit mit Ereignissen. Ich habe mir ja überlegt, neben dem Hauptthread zwei weitere Threads zu nutzen: ThreadDownload und ThreadVerarbeitung. In ThreadDownload werden nacheinander alle Dateien heruntergeladen. Sobald eine Datei heruntergeladen ist, wird der Thread ThreadVerarbeitung gestartet. Wie übergebe ich den ThreadVerarbeitung nun die zweite Datei, sobald diese heruntergeladen ist, über ein Ereignis? Oder ist dieser Design-Ansatz nicht so toll?

Letzte Frage 😉 Sollt man der Übersicht nicht allen Quellcode was man in einem extra Thread ablaufen lässt, in eine extra Datei/Klasse stecken? Ich hab mir einige Beispiele angeschaut, und diese sind fast immer in der gleichen Datei gewesen. Bei meinem herumprobieren, bin ich da auch schon durchaneinder gekommen, welche Methode jetzt in welchem Thread genutzt werden soll. Gibt es Gründe das auf die eine oder andere Weise zu machen, oder eben nicht?

Lg Birgit

946 Beiträge seit 2008
vor 14 Jahren

Wie übergebe ich den ThreadVerarbeitung nun die zweite Datei, sobald diese heruntergeladen ist, über ein Ereignis?

Am Besten syncronisierst du das einfach (siehe beispielsweise Applikation mit Warteschlange [SyncQueue<T>-Klasse]).
Eventuell kann dir für das Multithreading noch der Artikel hier weiterhelfen: [Artikel] Multi-Threaded Programmierung.

Wenn ich mehrere Dateien gleichzeit herunterlade, müssen sich diese ja die Bandbreite teilen, sprich bis ich eine Datei verarbeiten kann, würde in diesem Fall länger dauern.

Wahrscheinlich wird es schon etwas länger dauern. Andererseits lädt er die einzelnen Dateien vermutlich mehr als halb so schnell herunter, da du die Bandbreite sicher nicht alleine blockierst und viel von dem Server abhängt.
Am Schnellsten ginge es imho, wenn du schon den Teil bearbeitest, der gerade heruntergeladen wurde.
Ob dir dieser Mehraufwand für etwas mehr Performanz wert ist, musst allerdings du selbst einschätzen.

Sollt man der Übersicht nicht allen Quellcode was man in einem extra Thread ablaufen lässt, in eine extra Datei/Klasse stecken?

Also ich mache es nicht so. Wenn du ein durcheinander mit den Funktionen hast, würde ich das eventuell auf verschiedene Klassen aufteilen.
Diese Frage ist aber eher Ansichtssache wird praktisch jeder anders beantworten.

mfg
SeeQuark

49.485 Beiträge seit 2005
vor 14 Jahren

Hallo sbirg,

Es ist überall zu lesen, dass man von dem einen Thread nicht auf Daten eines anderen Thread zugreifen soll.

naja, ganz so pauschal nicht. Richtig ist, dass man wie überall sonst, nicht nur bei Threads, Werte die Parameter darstellen, auch wirklich per Parameter und nicht etwa über "globale" Variablen übergeben sollte. Außerdem muss man bei Zugriffen von mehreren Threads auf gemeinsame Daten immer auf die Synchronisation (z.B. lock) achten, damit es keine Inkonsistenzen gibt. Aber solche Zugriffe sind an sich schon erlaubt und oft auch nötig.

herbivore