![]() |
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:
Wie synchronisiere ich das thread-safe?
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"); } Und wie funktioniert diese BackgroundWorker-Komponente? ich habe da nicht wirklich was hilfreiches gefunden, was ich verstehen würde. |
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:
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.
void thread1_Event(object sender, EventArgs e)
{ if (InvokeRequired) { Invoke(new EventHandler(thread1_Event)); } else { // Mach, was du eigentlich machen willst } } 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. |
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..
|
Re: Threads und BackgroundWorker
Wie benutzt du denn die Monitore?
Code:
ist ja gleichbedeutend mit
lock (x)
{ DoSomething(); }
Code:
Von daher ist lock im Normalfall ja einfacher und weniger fehleranfällig anzuwenden.
System.Object obj = (System.Object)x;
System.Threading.Monitor.Enter(obj); try { DoSomething(); } finally { System.Threading.Monitor.Exit(obj); } |
Re: Threads und BackgroundWorker
Zitat:
In diesem Fall bieten sich entweder der Thread-Pool oder Asynchronous Delegates an:
Code:
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.
// 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); } |
Re: Threads und BackgroundWorker
Also ich habe es jetzt so gelöst:
Code:
Mit Monitore kenne ich mich auch in Java nicht aus.
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); } } 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? |
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. |
Re: Threads und BackgroundWorker
Zitat:
Code:
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.
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); } } |
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 ;) |
Re: Threads und BackgroundWorker
Zitat:
OT: Wenn ich das Leerzeichen weglasse, wird der Block unten zwar erkannt, erscheint dann aber in Times New Roman :stupid: Zitat:
Zitat:
Code:
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.
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(); } Zitat:
|
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:
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"); } |
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:
Mit
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(); ![]()
Delphi-Quellcode:
var future1 := async myCall('abc', 'def');
var future2 := async myCall('ghi', 'jkl'); |
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.
|
Re: Threads und BackgroundWorker
Hallo Michael,
Zitat:
Code:
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.
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{ lblStatusText.Text = e.ProgressPercentage.ToString() + resManager.GetString("BkWorkerStatusRunning"); } Gruß Jürgen |
Re: Threads und BackgroundWorker
Es passiert zur Laufzeit und zwar in dieser Zeile:
Code:
Deine Lösung dürfte also das Problem nicht beheben.
backgroundWorker1.ReportProgress((int)i / 10);
|
Re: Threads und BackgroundWorker
Zitat:
|
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. |
Re: Threads und BackgroundWorker
Zitat:
|
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"); } |
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... ;-) |
Re: Threads und BackgroundWorker
Das ist ein Argument. So funktioniert es:
Code:
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.
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); } } } |
Re: Threads und BackgroundWorker
Zitat:
Zitat:
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. |
Re: Threads und BackgroundWorker
Gut, dann ist Abort doch richtig.
allerdinsg scheint nichts zu passieren:
Code:
Laut Beispiel aus der Hilfe, müsste es aber so aussehen.
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(); } } |
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.
|
Re: Threads und BackgroundWorker
Zitat:
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(); } } |
Re: Threads und BackgroundWorker
Zitat:
Code:
Edit: Nach dem dritten Edit übernehme ich lieber keine Verantwortung über die Richtigkeit des Codes ;) .
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(); } } |
Re: Threads und BackgroundWorker
Jetzt schlägt allerdings die Aktualisierung im finally-Abschnitt fehl:
Zitat:
|
Re: Threads und BackgroundWorker
Zitat:
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. |
Re: Threads und BackgroundWorker
Ich verzweifele noch mal an diesen delegate und Invoke.
Code:
Will er auch nicht:
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"))); } } Zitat:
|
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. |
Re: Threads und BackgroundWorker
Gott sei dank ist in zwanzig Minuten Feierabend.
Und wie und wo implementiere ich, was der delegate machen soll? |
Re: Threads und BackgroundWorker
Zitat:
Code:
MethodInvoker call = delegate
{ SetStatusText(Resources.ThreadStatusFinish); }; Invoke(call); |
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.
|
Re: Threads und BackgroundWorker
Code:
delegate void SetStatusTextDelegate(string arg1);
Code:
So müsste es gehen. Ein delegat is nix anderes als ein methodenzeiger in delphi.
Invoke(new SetStatusTextDelegate(SetStatusText), new string[] { "String-parameter" });
|
Re: Threads und BackgroundWorker
Zitat:
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: |
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(); } } |
Re: Threads und BackgroundWorker
Zitat:
|
Re: Threads und BackgroundWorker
Puh. Also so habe ich es jetzt und so geht es auch:
Code:
Man war das eine schwere Geburt. Besten Dank noch mal an alle, die so viel Geduld hatten.
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")); } } |
Re: Threads und BackgroundWorker
Zitat:
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")); } } |
Re: Threads und BackgroundWorker
Oh, dann muss ich es noch mal ändern.
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 01:02 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz