C# Beim Buttonklick soll der Text eines Labels von 1 bis 100000 hochzählen - GUI hängt

Jack159

Lieutenant
Registriert
Dez. 2011
Beiträge
766
Hi,

Ich habe ein Fenster mit einem Button und einem Label. Beim klick auf den Button wird folgendes ausgeführt:

Code:
        private void button1_Click(object sender, EventArgs e) {
                for (int i = 0; i < 100000; i++) {
                     label1.Text = "" + i;
                }
        }

Das Problem ist, dass sich hierbei die GUI solange aufhängt, bis die for-Schleife zu Ende gelaufen ist.

Jetzt bin ich seit einiger Zeit schon am googeln und habe gelesen, dass man solche längeren Berechnungen nicht im GUI-Thread (wie hier) durchführen sollte, sondern besser in einem seperaten Thread und dann von dort aus die GUI updatet.
Ich habe zwar sehr viele Beispiele gefunden, aber nur unvollständige Code-Schnipsel wo einiges nicht klar ist. Auf manchen Seiten dann mit Lambda, dann ohne, dann mal 100 Zeilen lang, dann mal nur 1 Zeile lang....

Das einzige was geklappt hat, man aber nicht nutzen sollte, ist Application.Run().

Ganz einfach gefragt:
Was muss ich hier im Programm ändern, damit es funktioniert?
 
Ich würde das Hochzählen in einem neuen Thread ausführen und das Update des Labels über ein Invoke machen.

Code:
(new System.Threading.Thread(() => 
{
  for (int i = 0; i < 100000; i++)
    {
     Invoke((Action)delegate()
     {
       label1.Text = "" + i;
     });
    }
})).Start();

Edit: Gerade war ein Fehler drin. Dieser Code funktioniert.
 
Zuletzt bearbeitet:
can320 schrieb:
Windows Forms?

http://www.mycsharp.de/wbb2/thread.php?threadid=27992



Du meinst wohl DoEvents() - das sollte man tatsächlich nicht benutzen.


Andere Themen:
http://www.mycsharp.de/wbb2/board.php?boardid=70


Genau diese Seite hatte ich u.a. auch gefunden. Dort wird folgendes vorgeschlagen:

Code:
void DoSomethingExpensiveClick (Object objSender, EventArgs e)
{
   new Thread (DoSomethingExpensive).Start ();
}

void DoSomethingExpensive ()
{
   bool cancel;
   int percent = 0;

   do {
      Thread.Sleep (200); // simuliert aufwändige Aktion
      percent += 10;
      cancel = (bool)this.Invoke ((Func<int,bool>)DoCheapGuiAccess, percent); // sinnvoll
   } while (percent < 100 && !cancel);
}

bool DoCheapGuiAccess (int percent)
{
   // Do cheap GUI access
   // ...
}

Die Erstellung und den Start des Thread verstehe ich noch. Aber:
- was bedeutet denn "cancel = (bool)this.Invoke ((Func<int,bool>)DoCheapGuiAccess, percent);" ?
- vorallem: Func<int,bool> ?


Geht das nicht irgendwie einfacher/verständlicher? (Ich frage deshalb, weil man oft Beispiele findet, die unnötig verkompliziert werden).
 
Windows Forms oder WPF? Welche .NET Version?

Bei WPF und .NET 4.5:

Code:
Task.Run(async () =>
        {
            for (int i = 0; i < 100000; i++)
            {
                await Dispatcher.InvokeAsync(() => label1.Content = "" + i); // update ui
                await Task.Delay(100); // sleep 100ms
            }
        });

*edit*
Also Windows Forms? Darf man fragen warum nicht WPF? Anforderungen?!
Außerdem ist es in den meisten Fällen nicht wirklich sinnvoll bzw. nicht nötig einen neuen Thread zu erstellen. Die neue Task Library in .Net 4.5 ist Gold wert und sollte für die meisten Sachen verwendet werden. :)

*edit2*
Um das ganze Zeug mit Func<.... und Action... zu verstehen musst du das delegates in C# durchschauen und dann kannste c# Func und Action googeln.
 
Zuletzt bearbeitet:
Jack159 schrieb:
Genau diese Seite hatte ich u.a. auch gefunden. Dort wird folgendes vorgeschlagen:

- was bedeutet denn "cancel = (bool)this.Invoke ((Func<int,bool>)DoCheapGuiAccess, percent);" ?
- vorallem: Func<int,bool> ?

Ist für Programmieranfänger doch etwas hoch:
http://msdn.microsoft.com/de-de/library/bb549151(v=vs.110).aspx

Übergabewert an DoCheapGuiAccess wäre dann vom Typ int und der Rückgabewert den DoCheapGuiAccess wäre vom Typ bool. Außerdem bietet es die Möglichkeit den Vorgang abzubrechen.

Es ginge auch mit:

Code:
        private delegate void InvokeTest(string text);
        private bool mThreadLäuft = false;

        private void button1_Click(object sender, EventArgs e)
        {
            if (!mThreadLäuft) // falls man 2x auf den Button klickt, wird es trotzdem nur 1x ausgeführt
            {
                mThreadLäuft = true;
                Thread t = new Thread(new ThreadStart(MeinThread));
                t.Start();
            }
        }

        private void MeinThread()
        {
            int i = 0;

            for (i = 0; i < 100000; i++)
            {
                this.Invoke(new InvokeTest(UpdateGui), "" + i.ToString());
            }

            mThreadLäuft = false;
        }

        private void UpdateGui(string meinText)
        {
            label1.Text = meinText;
        }

Gibt aber nen Crash, falls man zwischendurch das Programm schließt. Man könnte beim Beenden abfragen ob der Thread noch läuft oder das ganze per Backgroundworker lösen.


Ansonsten ist die Lösung per Task.Run einfacher und man spart sich unnötige zusätzliche Methoden/den Delgate
 
Zuletzt bearbeitet von einem Moderator:
BlooDFreeZe schrieb:
Windows Forms oder WPF? Welche .NET Version?

Bei WPF und .NET 4.5:

Code:
Task.Run(async () =>
        {
            for (int i = 0; i < 100000; i++)
            {
                await Dispatcher.InvokeAsync(() => label1.Content = "" + i); // update ui
                await Task.Delay(100); // sleep 100ms
            }
        });

*edit*
Also Windows Forms? Darf man fragen warum nicht WPF? Anforderungen?!
Außerdem ist es in den meisten Fällen nicht wirklich sinnvoll bzw. nicht nötig einen neuen Thread zu erstellen. Die neue Task Library in .Net 4.5 ist Gold wert und sollte für die meisten Sachen verwendet werden. :)

Das funktioniert zwar, aber ich verstehe nicht, was da genau passiert. Wo wird welche Zeile in welchem Thread ausgeführt?

Ich finde zwar die verschiedensten Wege (z.B. auf mycsharp), aber das Problem ist, dass überall nur Codeausschnitte stehen. Das Problem dabei ist, dass man dann nicht genau weiß, wo dieser Code dann steht, worauf sich "this" bezieht und aus welchem Thread das ganze aufgerufen wird.

Das einfachste wäre eine Erklärung für 2 Fälle:
- GUI-Zugriffe (innerhalb Rechenintensiver Methoden aus) innerhalb des GUI-Threads (wie hier im Startpost das Beispiel)
- GUI-Zugriffe ((innerhalb Rechenintensiver Methoden aus) innerhalb eines nicht-GUI-Threads.

Ich habe jetzt 1 Stunde mit googeln verbracht, und keine einzige Seite gefunden, in denen diese 2 klar getrennten Fälle einfach aufgezeigt werden...Stattdessen finde ich zig verschiedene Syntaxschreibweisen (einer hipper als die andere), was nur unnötig verwirrt.
 
Du solltest erstmal paar C# Bücher lesen. Internetforen & google sind nicht dazu da dir programmieren beizubringen - damit kannst du deine Programmierkenntnisse erweitern, aber nicht von 0 auf lernen.
 
Jack159 schrieb:
Das funktioniert zwar, aber ich verstehe nicht, was da genau passiert. Wo wird welche Zeile in welchem Thread ausgeführt?
Das ist gar nicht einfach zu beantworten :P
Task.Run führt die angegebenen Aktionen im einem ThreadPool Thread aus. Welcher das ist und ob ein neuer Thread erstellt wird oder nicht weiß man nicht. Spielt aber in dem Fall auch keine Rolle.
Das hat dich wahrscheinlich jetzt noch weniger weiter gebracht :P Das Problem ist halt, dass C# extrem viele Hilfsklassen zur Verfügung stellt und mit Tasks auch noch ein ganz neues Konzept eingeführt hat. Deshalb solltest du dich auf jeden Fall systematisch in Threads einlesen. Denn selbst wenn du den Code halbwegs verstehst, gibt es genug Nebeneffekte die auftreten können und dann wird es irgendwann unmöglich Ursachen für Fehler zu finden.

Jack159 schrieb:
Ich finde zwar die verschiedensten Wege (z.B. auf mycsharp), aber das Problem ist, dass überall nur Codeausschnitte stehen. Das Problem dabei ist, dass man dann nicht genau weiß, wo dieser Code dann steht, worauf sich "this" bezieht und aus welchem Thread das ganze aufgerufen wird.

Das einfachste wäre eine Erklärung für 2 Fälle:
- GUI-Zugriffe (innerhalb Rechenintensiver Methoden aus) innerhalb des GUI-Threads (wie hier im Startpost das Beispiel)
- GUI-Zugriffe ((innerhalb Rechenintensiver Methoden aus) innerhalb eines nicht-GUI-Threads.

Naja es ist ja so: Wenn du die GUI aktualisieren willst, musst du das aus dem GUI Thread heraus machen. Im Fall 1 kannst du also einfach dein Label setzen, da der Button_Click ja aus dem GUI Thread aufgerufen wird, also der Inhalt auch in diesem ausgeführt wird.
Warum man das nicht macht hast du ja auch schon festgestellt. Dadurch, dass der GUI Thread mit deiner Berechnung beschäftigt ist, kann er nicht mehr auf Interaktionen reagieren bzw. überhaupt etwas anderes anzeigen.
Es bleibt somit Fall 2. Du musst also deine Berechnung auf einem anderen Thread ausführen und um die GUI zu aktualisieren aus dem anderen Thread den GUI Thread dazu bringen eine Aktualisierung auszuführen.
Für ersteres gibt es unendlich viele Möglichkeiten und auch für den zweiten Punkt gibt es einige Alternativen. Das Patentrezept gibt es nicht, wobei meistens wohl Invoke(...) zum Aktualisieren der GUI die schönste Methode ist. Es wird also die bei Invoke in den Parametern angegebene Aktion im GUI Thread ausgeführt. (Wie da übergibt man eine Methode die ausgeführt wird?!?! --> in C# delegates einlesen, dann sind Lambda expressions etc. auch ganz einfach.)
Die entsprechende Invoke Methode wird von den Forms (Windows Forms) bzw. von den Fenstern (WPF) bereitgestellt. Also this.Dispatcher.Invoke(...) bezieht sich auf den Dispatcher den die Klasseninstanz des Fensters zur Verfügung stellt. Der erlaubt einem Dinge auf dem Thread auszuführen, der das Fenster zeichnet -> passt! Bei Windows Forms entsprechend einfach this.Invoke(...).
 
Zuletzt bearbeitet:
Zurück
Oben