Entwickler-Ecke

WinForms - Threads parallelisieren (for-Schleife)


sentropie - Fr 08.10.10 12:57
Titel: Threads parallelisieren (for-Schleife)
Hallo,

ich lerne momentan C# und versuche mich an einem WindowsForms-Programm, in welchem ich mit Threads herumspiele. Im Prinzip besteht das Fenster aus einem Button und zwei Textboxen. Beim Klick auf den Button soll in beiden Textboxen gleichzeitig ein Zähler hochlaufen. Bei mir passiert das jedoch schön nacheinander - auch wenn es zufällig erscheint, in welcher Box begonnen wird.

Hier kommt der Code. Ich freue mich auf Ratschläge.


C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace Forms_Threading
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private int max = 10000;

        private void fkt1()
        {
            MethodInvoker m1 = delegate
            {
                for (int j = 0; j <= max; j++)
                {
                    textBox1.Text = j.ToString();               
                    textBox1.Refresh();                    
                }
            };

            Invoke(m1);
        }

        private void fkt2()
        {
            MethodInvoker m2 = delegate
            {
                for (int i = 0; i <= max; i++)
                {
                    textBox2.Text = i.ToString();
                    textBox2.Refresh();
                }
            };

            Invoke(m2);                       
        }

        private void button1_Click(object sender, EventArgs e)
        {
            textBox1.Text = "";
            textBox2.Text = "";

           
            Thread t1 = new Thread(fkt1);
            Thread t2 = new Thread(fkt2);
            t1.IsBackground = true;
            t2.IsBackground = true;
            t1.Start();
            t2.Start();                    
        }
    }
}


Th69 - Fr 08.10.10 13:47

Du "invoke"st ja auch jeweils die ganze Schleife.
Du mußt nur den GUI-Teil (d.h. die TextBox-Methoden) per Invoke aufrufen.

Falls du schon mit VS 2010 und .NET 4.0 arbeitest, kannst du dir mal die "Task Parallel Library" (TPL) anschauen, s.a. http://www.c-sharp-forum.de/viewtopic.php?p=604486#604486


danielf - Fr 08.10.10 14:00

Und selbst dann laufen die Threads nicht gleichzeitig. Rechenzeit gibts nur einmal :) So dass mal thread1 und dann wieder thread2 dran ist.

Mit dem BackgroundWorker kannst du es ein bisschen (ressourcemässig) schlanker gestalten. Darüber hinaus beenden sich die BackgroundWorker wenn sich der Hauptthread beendet.

Besser wäre auch, wenn du nicht fkt1 und fkt2 machst, sondern das zu ändernde TextBox-Control der funktion mitgibst (z.B. hier [http://dotnet-snippets.de/dns/methode-mit-parameter-in-eigenem-thread-starten-SID583.aspx]).

Gruß


Implementation - Fr 08.10.10 14:26

user profile icondanielf hat folgendes geschrieben Zum zitierten Posting springen:
Und selbst dann laufen die Threads nicht gleichzeitig. Rechenzeit gibts nur einmal :) So dass mal thread1 und dann wieder thread2 dran ist.

Und was ist wenn man mehrere CPU's hat?
Oder eine CPU mit mehreren Kernen?
Dann läuft's gleichzeitig :wink:


danielf - Fr 08.10.10 14:28

Nein ;p

Weil es immer noch der gleiche Prozess ist .. und dieser läuft immer nur auf einem CPU.

Dann brauchst du schon Parallel Framework ;p


Kha - Fr 08.10.10 15:29

user profile icondanielf hat folgendes geschrieben Zum zitierten Posting springen:
Weil es immer noch der gleiche Prozess ist .. und dieser läuft immer nur auf einem CPU.
Äh, nein. Jeder Thread kann pro Time Slice auf jedem beliebigen Kern laufen, solange keine Affinity festgelegt ist.

user profile icondanielf hat folgendes geschrieben Zum zitierten Posting springen:
Dann brauchst du schon Parallel Framework ;p
Was natürlich auch nur einen Prozess benutzt und darin einen geschönten Threadpool ;) .

@user profile iconsentropie: :welcome: !


danielf - Fr 08.10.10 15:39

gefährliches Halbwissen ...

Da hab ich den Anlass doch gleich mal genutzt um MSDN (Threads und Threading [http://msdn.microsoft.com/de-de/library/6kac2kdh.aspx]) zu lesen.

Zitat:
Dies ist bei Systemen mit mehreren Prozessoren auch tatsächlich der Fall, bei denen die ausführbaren Threads unter den verfügbaren Prozessoren aufgeteilt werden.


Zitat:
Ohne weitere Veränderungen würde dieselbe Anwendung auf einem Computer mit mehreren Prozessoren noch weitaus bessere Ergebnisse liefern.


Na dann bin ich ja schon voll MultiCore unterwegs ;)


sentropie - Fr 08.10.10 18:21

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Du "invoke"st ja auch jeweils die ganze Schleife.
Du mußt nur den GUI-Teil (d.h. die TextBox-Methoden) per Invoke aufrufen.

Falls du schon mit VS 2010 und .NET 4.0 arbeitest, kannst du dir mal die "Task Parallel Library" (TPL) anschauen, s.a. http://www.c-sharp-forum.de/viewtopic.php?p=604486#604486

Danke, nun klappt das schon mal.

Bei Gelegenheit werde ich mir mal die 4.0er Version anschauen. Aber wenn es auch "klassisch" geht, warum nicht? Daher habe ich erstmal so gemacht.

user profile icondanielf hat folgendes geschrieben Zum zitierten Posting springen:
[...]
Mit dem BackgroundWorker kannst du es ein bisschen (ressourcemässig) schlanker gestalten. Darüber hinaus beenden sich die BackgroundWorker wenn sich der Hauptthread beendet.

Besser wäre auch, wenn du nicht fkt1 und fkt2 machst, sondern das zu ändernde TextBox-Control der funktion mitgibst (z.B. [url=http://dotnet-snippets.de/dns/methode-mit-parameter-in-eigenem-thread-starten-SID583.aspx]hier[/url]).

BackgroundWorker sollte ich mir in der Tat einmal genauer anschauen. Allerdings fand ich das auf den ersten Blick nicht so leicht verständlich. Aber mal gucken.
Alles in eine Funktion wäre eine weitere Verbesserungsmöglichkeit. Auch etwas für die nahe Zukunft. :)

@user profile iconKha: Danke. :)

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

Jetzt, wo diese Sache läuft, möchte ich einen Schritt weitergehen. Ich habe einen Button und ein Textfeld hinzugefügt. Ich möchte nun, dass in dem Textfeld eine Zahl hochgezählt wird - und zwar bei jedem Klick +1. Ich bin jetzt so weit, dass die Klicks "registriert" werden, aber das Resultat sehe ich erst, nachdem die beiden Zähler-Threads fertig sind. (Also da steht z.B. eine 1, ich klick 3x und NACH dem Hochzählen erscheint eine 4. Das soll jedoch mehr oder weniger unmittelbar passieren.)

Das wäre der Button:


C#-Quelltext
1:
2:
3:
4:
5:
private void button2_Click(object sender, EventArgs e)
{
    int zahl = Convert.ToInt32(textBox3.Text) + 1;
    textBox3.Text = zahl.ToString();
}


Ich habe da auch schon (erfolglos) probiert, mit einem weiteren Thread anzukommen, aber das veränderte alles entweder zum schlechteren oder änderte nichts. Ich habe da auch ein Verständnisproblem. Thread 1 und 2 (der Hauptthread sei 0) erzeuge ich ja in der Click-Methode des Button1. Der weiß natürlich nichts von einem Thread im 2. Button (und umgekehrt). In einer Konsolenanwendung würde ich die Threads "ja einfach" in der main-Funktion erzeugen, aber wie muss ich hier vorgehen? Wo erzeugt man da richtigerweise die Threads?


Kha - Fr 08.10.10 19:25

user profile iconsentropie hat folgendes geschrieben Zum zitierten Posting springen:
Ich bin jetzt so weit, dass die Klicks "registriert" werden [...]
Wenn ich das bei mir ausprobiere, wird auch der Klick erst am Ende "visuell registriert". Und das liegt daran, dass dein Beispiel einfach noch nicht wirklich etwas mit Threads zu tun hat ;) . Da in den Nebenthreads nichts gearbeitet wird, nicht einmal ein Thread.Sleep, sind sie die ganze Zeit am Invoken und du hast den gleichen Effekt, als ob du die Schleifen im GUI-Thread ausführen würdest - es werden keine Events abgearbeitet.