Delphi-PRAXiS

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.

Luckie 14. Mär 2008 09:24

Re: Threads und BackgroundWorker
 
Es sollen nur Demos für mich sein, um zu verstehen, wie Threads unter .NET funktionieren.

Wenn ich es jetzt wie vorgeschlagen mache, bekomme ich folgende Fehlermeldung:
Zitat:

Dieser BackgroundWorker gibt an, dass er keinen Fortschritt meldet. Ändern Sie WorkerReportsProgress, um anzugeben, dass er einen Fortschritt meldet.
Mein Code:
Code:
private void button1_Click(object sender, EventArgs e)
        {
            if (rbThread.Checked)
            {
                Thread t = new Thread(ThreadProc);
                t.Start();              
                lblStatusText.Text = resManager.GetString("ThreadStatusFinish");
            }
            else
            {
                backgroundWorker1.RunWorkerAsync();
            }           
        }

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            for (int i = 0; i < 1000; i++)
            {
                backgroundWorker1.ReportProgress((int)i / 10);
               
            }
        }

        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            lblStatusText.Text = resManager.GetString("BkWorkerStatusRunning");
        }

Elvis 14. Mär 2008 10:09

Re: Threads und BackgroundWorker
 
Ich selbst bin ein Fan vom BeginInvoke/EndInvoke Pattern.
Ich kann dadurch X Operationen asynchron starten, andere Dinge erledigen, und dann wenn ich das Ergebnis brauche, wartet der Code halt auf den Rückgabewert oder er hat ihn schon.

Das ganze lässt sich auch so verpacken, dass man sich eine Factory für Delegates baut, die dann entweder warten oder den bestehenden Wert zurück geben.

Um nicht soviel drumrum posten zu müssen, habe ich es mal nur mit lokalen Delegates gelöst: (Kein Delphi Code!)
Delphi-Quellcode:
var r : Random := new Random();

var myCall := method(parameter1, parameter2 : String) : Integer;
begin
  Thread.Sleep(5000);
  exit r.Next(0, 1000);
end;

var getAsyncCall := method(parameter1, parameter2 : String) : Func<Integer>;
begin
  var resultRef := new class(Value := default(nullable Integer));
  var getValue : AsyncCallback := method(ar : IAsyncResult);
  begin
    locking resultRef do
      if not assigned(resultRef.Value) then
        resultRef.Value := myCall.EndInvoke(ar);
  end;

  var call := myCall.BeginInvoke(parameter1,
                                 parameter2,
                                 getValue,
                                 nil);
  result := method : Integer;
  begin
    getValue(call);
    exit valueOrDefault(resultRef.Value);
  end;
end;

var async1 := getAsyncCall('abc', 'def');
var async2 := getAsyncCall('ghi', 'jkl');

// do soemthing...
Thread.Sleep(2500);

var result1 := async1();
var result2 := async2();
var result3 := async1();
Mit Futures wird das dann schon zu einem totalen no-brainer. :-)
Delphi-Quellcode:
var future1 := async myCall('abc', 'def');
var future2 := async myCall('ghi', 'jkl');

Luckie 14. Mär 2008 10:13

Re: Threads und BackgroundWorker
 
Langsam, langsam langsam. Anstatt mit immer neuen Lösungen zu kommen, wäre es mir lieb, wenn wir erstmal meine Probleme mit den vorhandenen Lösungen besprechen könnten.

Jürgen Thomas 14. Mär 2008 10:18

Re: Threads und BackgroundWorker
 
Hallo Michael,

Zitat:

Zitat von Luckie
Wenn ich es jetzt wie vorgeschlagen mache, bekomme ich folgende Fehlermeldung:
Zitat:

Dieser BackgroundWorker gibt an, dass er keinen Fortschritt meldet. Ändern Sie WorkerReportsProgress, um anzugeben, dass er einen Fortschritt meldet.

Anscheinend versteht der Compiler (oder passiert es doch zur Laufzeit?), dass Deine Fortschrittsmeldung statisch ist, aber nicht den aktuellen Status verwendet. Ändere diese Methode etwa so:

Code:
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    lblStatusText.Text = e.ProgressPercentage.ToString()
        + resManager.GetString("BkWorkerStatusRunning");
}
Das kann natürlich mit String.Format() o.a. sehr verschönert werden. Das Prinzip ist aber, dass Du über ProgressChangedEventArgs.ProgressPercentage den aktuellen Stand (wie er über ReportProgress gesetzt wird) und über ProgressChangedEventArgs.UserState sogar einen bestimmten Wert (z.B. den aktuellen Datensatz, der natürlich ebenfalls gesetzt werden müsste) erhältst.

Gruß Jürgen

Luckie 14. Mär 2008 10:43

Re: Threads und BackgroundWorker
 
Es passiert zur Laufzeit und zwar in dieser Zeile:
Code:
backgroundWorker1.ReportProgress((int)i / 10);
Deine Lösung dürfte also das Problem nicht beheben.

Elvis 14. Mär 2008 10:51

Re: Threads und BackgroundWorker
 
Zitat:

Zitat von Luckie
Es passiert zur Laufzeit und zwar in dieser Zeile:
Code:
backgroundWorker1.ReportProgress((int)i / 10);
Deine Lösung dürfte also das Problem nicht beheben.

Hast du denn die Eigenschaft gesetzt, die er dir da als nicht gesetzt gemeldet hat?

Alter Mann 14. Mär 2008 11:02

Re: Threads und BackgroundWorker
 
Hi Luki,

in der MSDN gibt es ein leicht verständliches Beispiel zum BackgroundWorker, nennt sich:
"Ausführen eines Vorgangs im Hintergrund" und ist unter Beispiele BackgroundWorker-Klasse zu finden.

Luckie 14. Mär 2008 11:04

Re: Threads und BackgroundWorker
 
Zitat:

Zitat von Elvis
Hast du denn die Eigenschaft gesetzt, die er dir da als nicht gesetzt gemeldet hat?

Ich versinke dann mal eben im Boden - bin gleich wieder da.

Luckie 14. Mär 2008 11:56

Re: Threads und BackgroundWorker
 
So wir haben es gleich geschafft. Nur noch zwei, drei Dinge.

Ich rufe die Methode CancelAsync() auf, um den BackgroundWorker zu stoppen. Allerdings schein dann das Fenster nicht mehr zu reagieren und gestoppt wird anscheinen auch nichts:
Code:
        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            if (backgroundWorker1.CancellationPending)
            {
                lblStatusText.Text = resManager.GetString("BkWorkerUserCancel");
                e.Cancel = true;
            }
            for (int i = 0; i < 10000000; i++)
            {
                backgroundWorker1.ReportProgress((int)i / 10);
                Thread.Sleep(0);
               
            }
        }

        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            lblStatusText.Text = resManager.GetString("BkWorkerStatusRunning");
            this.Update();
        }       

        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            lblStatusText.Text = resManager.GetString("BkWorkerStatusFinish");
        }

Elvis 14. Mär 2008 12:02

Re: Threads und BackgroundWorker
 
Wo in deinem Code prüfst du denn, dass er abrechen soll?
Außer direkt am Anfang vor der Schleife natürlich... ;-)

Luckie 14. Mär 2008 12:33

Re: Threads und BackgroundWorker
 
Das ist ein Argument. So funktioniert es:
Code:
        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
           
            for (int i = 0; i < 100000; i++)
            {
                if (backgroundWorker1.CancellationPending)
                {                   
                    e.Cancel = true;
                }
                else
                {
                    backgroundWorker1.ReportProgress((int)i / 10);
                    Thread.Sleep(0);
                }
            }
        }
Und wie breche ich einen mit der Thread-Klasse erstellten Thread ab? Abort löst eine Exception aus und Suspend lässt ihn nur pausieren. Irgendwie fehlt eine Methode zum terminieren.

Khabarakh 14. Mär 2008 12:55

Re: Threads und BackgroundWorker
 
Zitat:

Zitat von Luckie
Und wie breche ich einen mit der Thread-Klasse erstellten Thread ab? Abort löst eine Exception aus [...]

Wenn es eine ThreadAbortException ist, ist das goldrichtig :zwinker: :
Zitat:

When this method is invoked on a thread, the system throws a ThreadAbortException in the thread to abort it. ThreadAbortException is a special exception that can be caught by application code, but is re-thrown at the end of the catch block unless ResetAbort is called. ResetAbort cancels the request to abort, and prevents the ThreadAbortException from terminating the thread. Unexecuted finally blocks are executed before the thread is aborted.
Damit wird also garantiert, dass der Thread nicht einfach abgewürgt wird (was nicht gerade der BCL-Philosophie entspräche), sondern davor noch alle finally-Blöcke sauber durchlaufen werden. Mit genügend Rechten kann der Thread seinen Abbruch sogar selbst stoppen.

PS: Der Thread ist danach allerdings nicht mehr nutzbar und man benötigt einen neuen. Wenn das öfter geschehen soll, sollte man wohl besser einen ThreadPool-Thread benutzen und in diesem (wie in deinem BackgroundWorker-Code) ein Abbruch-Flag auswerten.

Luckie 14. Mär 2008 13:08

Re: Threads und BackgroundWorker
 
Gut, dann ist Abort doch richtig.

allerdinsg scheint nichts zu passieren:
Code:
        private void btnStop_Click(object sender, EventArgs e)
        {
            if (rbThread.Checked)
            {
                t.Abort();
            }
            else
            {
                backgroundWorker1.CancelAsync();
            }
        }
               

        private void thread1_Event(object sender, EventArgs e)
        {
            try
            {
                if (InvokeRequired)
                {
                    Invoke(new EventHandler(thread1_Event));
                }
                else
                {
                    for (int i = 0; i < 100000; i++)
                    {
                        lblStatusText.Text = resManager.GetString("ThreadStatusRunning");
                        this.Update();
                        Thread.Sleep(0);
                    }
                }
            }
            catch (ThreadAbortException tae)
            {
                lblStatusText.Text = resManager.GetString("ThreadStatusFinish");
                this.Update();
            }
        }

        private void ThreadProc()
        {
            try
            {
                thread1_Event(this, null);
            }
            catch (ThreadAbortException tae)
            {
                lblStatusText.Text = resManager.GetString("ThreadStatusFinish");
                this.Update();
            }
        }
Laut Beispiel aus der Hilfe, müsste es aber so aussehen.

Khabarakh 14. Mär 2008 13:22

Re: Threads und BackgroundWorker
 
Habe ich irgendwo oben schon zu deinem ersten Thread-Code geschrieben: Du invokst thread1_Event, also wird deine gesamte Schleife im Hauptthread ausgeführt und dort kann Abort schlecht wirken. Es wird warten, bis thread1_Event beendet ist und erst dann die Exception werfen.

Luckie 14. Mär 2008 13:28

Re: Threads und BackgroundWorker
 
Zitat:

Zitat von Khabarakh
Habe ich irgendwo oben schon zu deinem ersten Thread-Code geschrieben: Du invokst thread1_Event, also wird deine gesamte Schleife im Hauptthread ausgeführt und dort kann Abort schlecht wirken. Es wird warten, bis thread1_Event beendet ist und erst dann die Exception werfen.

Oh, dann habe ich ja gar nicht das was ich wollte. Mist. Wie mache ich es denn richtig?

Code:
private void button1_Click(object sender, EventArgs e)
        {
            if (rbThread.Checked)
            {
                try
                {
                    t = new Thread(ThreadProc);
                    t.Name = "Thead";
                    t.Start();
                }
                catch
                {

                }
            }
            else
            {
                backgroundWorker1.RunWorkerAsync();
            }
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            if (rbThread.Checked)
            {
                t.Abort();
            }
            else
            {
                backgroundWorker1.CancelAsync();
            }
        }
               

        private void thread1_Event(object sender, EventArgs e)
        {
            try
            {
                if (InvokeRequired)
                {
                    Invoke(new EventHandler(thread1_Event));
                }
                else
                {
                    for (int i = 0; i < 100000; i++)
                    {
                        lblStatusText.Text = resManager.GetString("ThreadStatusRunning");
                        this.Update();
                        Thread.Sleep(0);
                    }
                }
            }
            catch (ThreadAbortException tae)
            {
                lblStatusText.Text = resManager.GetString("ThreadStatusFinish");
                this.Update();
            }
        }

        private void ThreadProc()
        {
            try
            {
                thread1_Event(this, null);
            }
            catch (ThreadAbortException tae)
            {
                lblStatusText.Text = resManager.GetString("ThreadStatusFinish");
                this.Update();
            }
        }

Khabarakh 14. Mär 2008 13:34

Re: Threads und BackgroundWorker
 
Zitat:

Zitat von Luckie
Wie mache ich es denn richtig?

Wie gesagt, ist ziemlich parallel zum BackgroundWorker:
Code:
        void thread1_Event()
        {
            lblStatusText.Text = resManager.GetString("ThreadStatusRunning");
            this.Update();
            Thread.Sleep(0);
        }

        void ThreadProc()
        {
            try
            {
                for (int i = 0; i < 100000; i++)
                {
                    Invoke(new MethodInvoker(thread1_Event)); // ich kann durchaus verstehen, dass nicht jeder anonyme Methoden mag *g*
                } 
            }
            catch (ThreadAbortException tae) // hier würde ich eher finally benutzen
            {
                lblStatusText.Text = resManager.GetString("ThreadStatusFinish"); // und hier sollte es eine Cross-Call-Exception hageln
                this.Update();
            }
        }
Edit: Nach dem dritten Edit übernehme ich lieber keine Verantwortung über die Richtigkeit des Codes ;) .

Luckie 14. Mär 2008 14:07

Re: Threads und BackgroundWorker
 
Jetzt schlägt allerdings die Aktualisierung im finally-Abschnitt fehl:
Zitat:

Ungültiger threadübergreifender Vorgang: Der Zugriff auf das Steuerelement lblStatusText erfolgte von einem anderen Thread als dem Thread, für den es erstellt wurde.
:wall:

Elvis 14. Mär 2008 14:09

Re: Threads und BackgroundWorker
 
Zitat:

Zitat von Luckie
Jetzt schlägt allerdings die Aktualisierung im finally-Abschnitt fehl:
Zitat:

Ungültiger threadübergreifender Vorgang: Der Zugriff auf das Steuerelement lblStatusText erfolgte von einem anderen Thread als dem Thread, für den es erstellt wurde.
:wall:

Na weil da kein Invoke benutzt wurde.
Du stellst dich auch manchmal an... ;-)

Übrigens musst du nicht so umständlich mit ResourceManagern arbeiten.
Das VS erzeugt autom. statische Eigenschaften für jeden Wert in der Resource Datei, so dass du direkt auf den Namen mit dem richtigen Typ zugreifen kannst.

Luckie 14. Mär 2008 14:22

Re: Threads und BackgroundWorker
 
Ich verzweifele noch mal an diesen delegate und Invoke.
Code:
private void SetStatusText(string Text)
        {
            lblStatusText.Text = Text;
            this.Update();
        }

        void ThreadProc()
        {
            try
            {
                for (int i = 0; i < 100000; i++)
                {
                    Invoke(new MethodInvoker(thread_Event));
                    Thread.Sleep(0);
                }
            }
            finally
            {
                Invoke(SetStatusText(resManager.GetString("ThreadStatusFinish")));              
            }
        }
Will er auch nicht:
Zitat:

The best overloaded method match for 'System.Windows.Forms.Control.Invoke(System.Delega te)' has some invalid arguments

phXql 14. Mär 2008 14:37

Re: Threads und BackgroundWorker
 
Das Problem ist, dass SetStatusText(resManager.GetString("ThreadStatusFi nish")) kein Delegat ist. Bei deinem anderen Invoke-Aufruf hast du das ja auch zuerst in den MethodInvoker-delegaten verpackt.
Du wirst dir also wahrscheinlich für deine SetStatusText(string) einen delegaten basteln müssen, dann die Funktion darin verpacken und dann gehts.

Luckie 14. Mär 2008 14:41

Re: Threads und BackgroundWorker
 
Gott sei dank ist in zwanzig Minuten Feierabend.

Und wie und wo implementiere ich, was der delegate machen soll?

Elvis 14. Mär 2008 14:42

Re: Threads und BackgroundWorker
 
Zitat:

Zitat von phXql
Das Problem ist, dass SetStatusText(resManager.GetString("ThreadStatusFi nish")) kein Delegat ist. Bei deinem anderen Invoke-Aufruf hast du das ja auch zuerst in den MethodInvoker-delegaten verpackt.

Oder einfach eine anonyme Methode ohne Parameter:
Code:
MethodInvoker call = delegate
{
  SetStatusText(Resources.ThreadStatusFinish);
};

Invoke(call);

Luckie 14. Mär 2008 14:50

Re: Threads und BackgroundWorker
 
Langsam. Wie eght das jetzt mit dem delegate genau? Ich wollte die Methode auch an anderer Stelle nutzen, als normale Methode.

phXql 14. Mär 2008 14:56

Re: Threads und BackgroundWorker
 
Code:
delegate void SetStatusTextDelegate(string arg1);
Code:
Invoke(new SetStatusTextDelegate(SetStatusText), new string[] { "String-parameter" });
So müsste es gehen. Ein delegat is nix anderes als ein methodenzeiger in delphi.

Elvis 14. Mär 2008 14:58

Re: Threads und BackgroundWorker
 
Zitat:

Zitat von Luckie
Langsam. Wie eght das jetzt mit dem delegate genau? Ich wollte die Methode auch an anderer Stelle nutzen, als normale Methode.

Ein Delegate ist ein Hybride aus Methodenzeiger und Funktionszeiger. (man ihm kann beides zuweisen)

Da man aber oft nicht von der Signatur abhängig sein will, wurden anonyme methoden entwickelt.
Da du ganz sicher keine Methode in deiner Klasse für jeden kleinen Furz haben willst, kannst du auch anonyme Methoden nehmen.
Die sind nur lokal sichtbar und müllen dir deinen Code nicht zu. Im Code existieren sie für dich nur als Delegate, nie als normale Methode.

Du kannst so auch Werte übergeben ohne von den Parametern abhängig zu sein, aber das sparen wir uns lieber für den nächsten Thread auf. :mrgreen:

Luckie 14. Mär 2008 15:08

Re: Threads und BackgroundWorker
 
:firejump: So geht es. :P

Noch ein letzter Punkt, dann haben wir es geschafft. ;)

Die BackgroundWorker Komponente hat j aein Ereignis, welches ausgelöst wird, wenn er fertig ist. Wie kann ich jetzt mit der Thread-Klasse warten? Mittels join friert mir die ganze Anwendung ein:

Code:
private void button1_Click(object sender, EventArgs e)
        {
            if (rbThread.Checked)
            {               
                t = new Thread(ThreadProc);
                t.Name = "Thead";
                t.Start();
                t.Join()
                SetStatusText("ThreadStatusFinish");              
            }
            else
            {
                backgroundWorker1.RunWorkerAsync();
            }
        }

Elvis 14. Mär 2008 15:14

Re: Threads und BackgroundWorker
 
Zitat:

Zitat von Luckie
:firejump: So geht es. :P

Noch ein letzter Punkt, dann haben wir es geschafft. ;)

Die BackgroundWorker Komponente hat j aein Ereignis, welches ausgelöst wird, wenn er fertig ist. Wie kann ich jetzt mit der Thread-Klasse warten? Mittels join friert mir die ganze Anwendung ein

Indem du es einfach selbst auslöst?

Luckie 14. Mär 2008 15:50

Re: Threads und BackgroundWorker
 
Puh. Also so habe ich es jetzt und so geht es auch:

Code:
private delegate void ThreadFinishEventHandler(object Sender);
        private event ThreadFinishEventHandler OnThreadFinishHandler;  

private void OnThreadFinish()
        {
            if (OnThreadFinishHandler != null)
                OnThreadFinishHandler(this);
        } 

private void ThreadFinish(object Sender)
        {
            SetStatusText(resManager.GetString("ThreadStatusFinish"));
        }

        private void ThreadProc()
        {
            try
            {
                for (int i = 0; i < 100000; i++)
                {
                    Invoke(new MethodInvoker(thread_Event));
                    Thread.Sleep(0);
                }
                this.OnThreadFinishHandler += ThreadFinish;
            }
            finally
            {
                SetStatusTextDelegate sd = SetStatusText;
                Invoke(sd, resManager.GetString("ThreadStatusFinish"));              
            }
        }
Man war das eine schwere Geburt. Besten Dank noch mal an alle, die so viel Geduld hatten.

Elvis 14. Mär 2008 16:26

Re: Threads und BackgroundWorker
 
Zitat:

Zitat von Luckie
Puh. Also so habe ich es jetzt und so geht es auch:

Nein, ist falsch.
Du registrierst am Ende ThreadFinish als Handler für einen falsch benannten Event: OnThreadFinishHandler.
Das macht doch keinen Sinn?
Du würdest den EVENT am Ende feuern, und irgendjemand hätte sich für den registriert.
Code:
private void ThreadProc()
{
    try
    {
        for (int i = 0; i < 100000; i++)
        {
            Invoke(new MethodInvoker(thread_Event));
            Thread.Sleep(0);
        }
        ThreadFinishEventHandler h = this.ThreadFinished; // so sollte der Event heißen
        if(h != null)
          Invoke(h, this);
    }
    finally
    {
        SetStatusTextDelegate sd = SetStatusText;
        Invoke(sd, resManager.GetString("ThreadStatusFinish"));              
    }
}

Luckie 14. Mär 2008 20:42

Re: Threads und BackgroundWorker
 
Oh, dann muss ich es noch mal ändern.


Alle Zeitangaben in WEZ +1. Es ist jetzt 09:13 Uhr.

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