Delphi-PRAXiS
Seite 1 von 4  1 23     Letzte »    

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Win32/Win64 API (native code) (https://www.delphipraxis.net/17-win32-win64-api-native-code/)
-   -   C# Threads und BackgroundWorker (https://www.delphipraxis.net/110126-threads-und-backgroundworker.html)

Luckie 13. Mär 2008 11:10


Threads und BackgroundWorker
 
Ich versuche gerade rauszufinden, wie man mit Threads unter C# arbeitet. Anscheinend gibt es da ja zwei Möglichkeiten, entweder mit dem Thread-Objekt oder der BackgroundWorker Komponente.

Ich würde gerne beides verstehen und benutzen können. Nur leider scheitere ich schon bei der Thread-Klasse:
Code:
private void ThreadProc()
        {
            lock (this.listBox1)
            {
                for (int i = 0; i < 100; i++)
                    listBox1.Items.Add("Thread");
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Thread t = new Thread(ThreadProc);
            t.Start();    
            for (int i = 0; i < 100; i++)
                listBox1.Items.Add("main");
        }
Wie synchronisiere ich das thread-safe?

Und wie funktioniert diese BackgroundWorker-Komponente? ich habe da nicht wirklich was hilfreiches gefunden, was ich verstehen würde.

OregonGhost 13. Mär 2008 11:21

Re: Threads und BackgroundWorker
 
Synchronisieren zum Beispiel so (häufig z.B. in einem Ereignishandler anzutreffen, der auf ein im Thread ausgelöstes Ereignis reagiert):
Code:
void thread1_Event(object sender, EventArgs e)
{
    if (InvokeRequired) {
        Invoke(new EventHandler(thread1_Event));
    } else {
        // Mach, was du eigentlich machen willst
    }
}
Nicht schön, aber geht. Dieser Eventhandler funktioniert dann von jedem Thread aus. Google findet auch noch die eine oder andere Klasse, die solche Dinge etwas vereinfacht.

Wenn du nicht auf die GUI zugreifen willst, ist lock() schon der richtige Ansatz zur Synchronisierung.
Edit: Gerade noch einmal über deinen Code geschaut. Du hast da was missverstanden: lock ist kein Äquivalent zu Delphis Synchronize. lock entspricht grob einer CriticalSection unter Windows, oder in allen anderen Threading-Bibliotheken einem Mutex. Damit sperrst du also den Zugriff auf ein Objekt, auf das nicht mehrere Objekte gleichzeitig zugreifen sollen. Synchronize macht hingegen im Prinzip ein Invoke.

Ach ja: Du kannst natürlich auch, anstatt umständlich zu prüfen etc., von vornherein vom Thread aus ein Invoke oder BeginInvoke ausführen. Das kommt dann am ehesten der Synchronize-Geschichte nahe.

BackgroundWorker funktioniert etwas anders, außer dem DoWork-Event werden alle Events automatisch im richtigen Thread ausgelöst. Vereinfacht ausgedrückt: Auf Form ziehen, im DoWork-Ereignis deine Dinge erledigen und das dann per RunWorkerAsync aufrufen. Die übrigen Ereignisse und Methoden sind zur zusätzlichen Kommunikation gedacht.

Medium 13. Mär 2008 13:19

Re: Threads und BackgroundWorker
 
Synchronisation mache ich am liebsten über Monitore. Schau dir mal die Klasse Monitor an - das Konzept ist das gleiche wie bei Java z.B..

OregonGhost 13. Mär 2008 13:28

Re: Threads und BackgroundWorker
 
Wie benutzt du denn die Monitore?
Code:
lock (x)
{
    DoSomething();
}
ist ja gleichbedeutend mit
Code:
System.Object obj = (System.Object)x;
System.Threading.Monitor.Enter(obj);
try
{
    DoSomething();
}
finally
{
    System.Threading.Monitor.Exit(obj);
}
Von daher ist lock im Normalfall ja einfacher und weniger fehleranfällig anzuwenden.

Khabarakh 13. Mär 2008 13:32

Re: Threads und BackgroundWorker
 
Zitat:

Zitat von OregonGhost
Ach ja: Du kannst natürlich auch, anstatt umständlich zu prüfen etc., von vornherein vom Thread aus ein Invoke oder BeginInvoke ausführen. Das kommt dann am ehesten der Synchronize-Geschichte nahe.

Das würde ich auch empfehlen, dadurch habe ich InvokeRequired noch nie benötigt.

In diesem Fall bieten sich entweder der Thread-Pool oder Asynchronous Delegates an:
Code:
// ThreadPool
// Der Name sagt alles: anstatt jedes Mal einen neuen Thread zu
// erstellen, wird wenn möglich ein alter aus dem Pool benutzt.
// Kein Rückgabewert
// => Nach Ende noch einmal Invoke oder andersweitige Übergabe
ThreadPool.QueueUserWorkItem((state) => {
   int sum = 0;
   for (int i = 1; i <= 10005; i++) {
      sum += i;
      if (i % 1000 == 0)
          // Achtung: Invoke ist synchron
            // asynchron: BeginInvoke
            Invoke(new Action<int>(AddItem), sum);
            // oder inline:
            // Invoke(new Action(() => listBox.Items.Add(i)));
   }
   Invoke(new Action(() => listBox.Items.Add(sum)));
}, null);


 //3. Asynchronous Delegates
 //Benutzen intern ThreadPool
 //Rückgabewert möglich
Func<int> func =
    () => {
        int sum = 0;
        for (int i = 1; i <= 10005; i++) {
            sum += i;
            if (i % 1000 == 0)
                Invoke(new Action(() => listBox.Items.Add(sum)));
        }
        return sum;
    };

func.BeginInvoke(
    (IAsyncResult result) =>
      Invoke(new Action(() => AddItem(func.EndInvoke(result)))),
    null);

[...]

void AddItem(int i)
{
   listBox.Items.Add(i);
}
Wenn ich's recht bedenke, gibt es aber keinen wirklichen Vorteil der Delegates im Vergleich zum Thread-Pool, weshalb ich mich mit ihnen gar nicht erst auseinandersetzen würde.

Luckie 13. Mär 2008 14:15

Re: Threads und BackgroundWorker
 
Also ich habe es jetzt so gelöst:
Code:
private void thread1_Event(object sender, EventArgs e)
        {
            if (InvokeRequired)
            {
                Invoke(new EventHandler(thread1_Event));
            }
            else
            {
                for (int i = 0; i < 1000; i++)
                {
                    this.listBox1.Items.Add("Thread" + i.ToString());
                    this.listBox1.Update();
                    this.Text = "Thread";
                    this.Update();
                    Thread.Sleep(0);            
                }
            }
        }

        private void ThreadProc()
        {
            thread1_Event(this, null);    
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Thread t = new Thread( ThreadProc);
            t.Start();
            t.Priority = ThreadPriority.BelowNormal;
            for (int i = 0; i < 1000; i++)
            {
                this.listBox1.Items.Add("Main" + i.ToString());
                this.listBox1.Update();
                this.Text = "Main";
                this.Update();
                Thread.Sleep(0);
            }
        }
Mit Monitore kenne ich mich auch in Java nicht aus.

Jetzt mus sich nur noch rausfinden, wie das mit dem BackgroundWorker funktioniert.

Wo ist denn da der Unterschied bzw. was sind Vor- und/oder Nachteile?

OregonGhost 13. Mär 2008 14:43

Re: Threads und BackgroundWorker
 
Der BackgroundWorker nimmt dir die ganze Arbeit der Verwaltung ab, wenn dein Thread nichts weiter macht als irgendwas zu arbeiten, seinen Fortschritt zu melden und schließlich zu sagen, dass er fertig ist. Das läuft Event-basiert und ist sehr einfach aufgebaut. Probier es mal aus, wenn du nicht mehr Informationen von deinen Threads brauchst, ist es vielleicht sogar besser als alles von Hand zu machen.

@InvokeRequired:
Was macht Invoke, wenn man schon im richtigen Thread ist? Es geht trotzdem den Umweg über die Nachrichtenschleife, oder? Mit manueller Prüfung per InvokeRequired kann man dann schnelleren Code schreiben, wenn dieser nicht thread-übergreifend aufgerufen wird, und der Thread muss nicht wissen, ob er die Funktion direkt ausführen darf oder nicht. Wie so oft, mehr Aufwand -> mehr Möglichkeiten.

Luckie 13. Mär 2008 14:53

Re: Threads und BackgroundWorker
 
Zitat:

Zitat von OregonGhost
BackgroundWorker funktioniert etwas anders, außer dem DoWork-Event werden alle Events automatisch im richtigen Thread ausgelöst. Vereinfacht ausgedrückt: Auf Form ziehen, im DoWork-Ereignis deine Dinge erledigen und das dann per RunWorkerAsync aufrufen. Die übrigen Ereignisse und Methoden sind zur zusätzlichen Kommunikation gedacht.

Das habe ich jetzt mal gemacht:
Code:
private void button1_Click(object sender, EventArgs e)
        {
            if (rbThread.Checked)
            {
                Thread t = new Thread(ThreadProc);
                t.Start();
            }
            else
            {
                backgroundWorker1.RunWorkerAsync();
            }
            for (int i = 0; i < 1000; i++)
            {
                this.listBox1.Items.Add("Main" + i.ToString());
                this.listBox1.Update();
                this.Text = "Main";
                this.Update();
                Thread.Sleep(0);
            }
        }

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            for (int i = 0; i < 1000; i++)
            {
                this.listBox1.Items.Add("BackgroundWorker" + i.ToString());
                this.listBox1.Update();
                this.Text = "BackgroundWorker";
                this.Update();
                Thread.Sleep(0);
            }
        }
Allerdings bekooe ich bei this.listBox1.Items.Add("BackgroundWorker" + i.ToString()); eine Fehlermeldung, dass ich versuche aus einem anderen Thread auf ein Steuerelement zu zugreifen.

OregonGhost 13. Mär 2008 15:23

Re: Threads und BackgroundWorker
 
Ja. DoWork wird in einem separaten Thread ausgeführt. Aber du kannst von hier aus z.B. ReportProgress aufrufen und in deinem Formular auf das ProgressChanged-Ereignis reagieren.

PS: Psst, mit [ C][ /C] sieht der Code viel besser aus ;)

Khabarakh 13. Mär 2008 17:05

Re: Threads und BackgroundWorker
 
Zitat:

Zitat von OregonGhost
PS: Psst, mit [ C][ /C] sieht der Code viel besser aus ;)

mit [csharp ] noch besser :mrgreen: .
OT: Wenn ich das Leerzeichen weglasse, wird der Block unten zwar erkannt, erscheint dann aber in Times New Roman :stupid:
Zitat:

Zitat von Luckie
Also ich habe es jetzt so gelöst:
<schnipp />

Du führst die gesamte Schleife im Hauptthread aus, das ist glaube ich nicht so gedacht ;) . Wobei sich das bei deinem Beispiel so oder so nicht verhindern lässt, denn es werden ja nur Controls manipuliert; der zweite Thread ist nichts anderes als zusätzlicher Overhead. Ist das ListBox-Befüllen denn das Ziel oder geht es um etwas Anderes?
Zitat:

Zitat von Luckie
Jetzt mus sich nur noch rausfinden, wie das mit dem BackgroundWorker funktioniert.

So dürfte dein Beispiel aussehen:
Code:
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
   for (int i = 0; i < 1000; i++) {
      backgroundWorker.ReportProgress(42);
      Thread.Sleep(0);
   }
}

void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
   listBox1.Items.Add("Main" + i.ToString());
   listBox1.Update();
   Text = "Main";
   Update();
}
Naja, wir haben im Vergleich zum Thread-Pool Invoke durch ReportProgress ersetzt, das war's wohl schon. Für mich ziehe ich da das gleiche Fazit wie bei den Delegates: Wenn ich mit der universellen Methode fast gleich wenig Arbeit habe wie mit der speziellen, dann bleibe ich lieber bei ersterer.

Zitat:

Zitat von OregonGhost
@InvokeRequired:

Nehmen wir meinen Code von oben: dort ist doch überall erkennbar, ob ich mich in einem Thread befinde und ob ich deshalb AddItem in Invoke wrappen muss oder nicht. Warum sollte ich dann noch InvokeRequired in AddItem abfragen? Wenn man dann noch Form- und Thread-Code fein säuberlich trennt, kann es noch weniger Verwechslungen geben.


Alle Zeitangaben in WEZ +1. Es ist jetzt 06:43 Uhr.
Seite 1 von 4  1 23     Letzte »    

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz