Hallöchen,
ich bin vor knapp 7 Tagen mit C# angefangen und hatte bisher nur Kenntnisse in PHP.
Ich hab mir ein Programm geschrieben mit dem ich Fotos verkleinern kann.
Ich nehme Beispielsweise 100 Fotos in die Liste, klicke auf Start und er ackert.
Das klappt alles soweit sehr gut.
Mir wird wie gewünscht eine ProgressBar angezeigt usw.
Sobald ich aber in dem Programm rumklicke (bin grad bei meiner eigenen Betaphase und will Fehler ausbügeln 😉) friert das Form ein, oben erscheint Keine Rückmeldung und ich muss warten bis das verkleinern durchgelaufen ist.
Also er arbeitet noch weiter.
Ich habe probiert jedes einzelne Element mittels .Enable = False zu blockieren, hab es auch schon mit this.Enable = True gemacht, aber das scheint nicht die Lösung meines Problems zu sein.
Ich hab hier im Forum schon danach gesucht aber keine Lösung gefunden.
Wäre super wenn mir da jemand auf die Sprünge helfen könnte.
Hallo Mani,
du musst alle Aktionen, die länger als Sekundenbruchteile dauern, in einen Threads auslagern.
herbivore
Danke, werd mich da mal einlesen.
grad in deinem fall isses recht einfach... probiers mal mit einem BackgroundWorker.
Hallo zusammen,
BackgroundWorker ist eine 2.0-Klasse, deren Vorteil mir nicht recht einleuchten will. Jedenfalls braucht man weniger Code und in flexibler, wenn man die Thread-Klasse verwendet.
herbivore
Hallo Herbivore,
aber es ist doch ein neues Feature von .NET 2.0, das kommt von Microsoft, und das kann nur besser sein, als alles andere bisher dagewesene blinzel 😉
Ciao
Norman-Timo
Ich vermute das liegt daran, dass herbivore nicht die VS-Ide benutzt. Backgroundworker kann man einfach aufs Formular ziehen, dann wird das Codeskelett erzeugt.
Ansonsten sehe ich auch keine Vorteile(dh. keine, nur Nachteile).
Hmmm, also im Grunde brauch ich ja keinen Hintergrundprozess.
Der User soll in dem Programm ja eh nix drücken dürfen solang die Verarbeitung läuft.
Hallo Mani,
du willst doch das Einfrieren verhindern. Selbst wenn der User nichts macht: auch das Neuzeichnen oder Verschieben des Fensters läuft im GUI-Thread und ist blockiert, wenn du den mit Beschlag belegst. Also musst du die lange Verarbeitung in einen anderen Prozess auslagern.
herbivore
Original von ikaros
Ich vermute das liegt daran, dass herbivore nicht die VS-Ide benutzt. Backgroundworker kann man einfach aufs Formular ziehen, dann wird das Codeskelett erzeugt.
Ansonsten sehe ich auch keine Vorteile(dh. keine, nur Nachteile).
Einfacheres Update des GUI von einem anderen Thread aus ohne BeginInvoke/EndInvoke/Delegate-Schwachsinn ist kein Grund?
Hallo Meli,
was denn für BeginInvoke/EndInvoke/Delegate-Schwachsinn?
Mal abgesehen davon, dass du um Delegaten auch beim BackgroundWorker nicht drum rum kommst, ist ein BeginInvoke doch 'ne simple Sache: Statt eine Methode aufzurufen, bittet man BeginInvoke das zu tun. That's all. Ist genauso einen Zeile wie ReportProgress.
Dagegen braucht man zum Starten eines Threads 1-2 Zeilen. Zum Starten eines BackgroundWorkers mindestens 5.
herbivore
Naja, ich finde es durchaus als komfortabel, die Komponente aufs Form zu ziehen und nur noch die Events ausprogrammieren zu müssen. Aus Nutzersicht schreibt man nur noch fachlichen Code, bekommt ein klares Gerüst für die Parameterübergabe, Abbrechen und Ergebnisbearbeitung. Die Einfachheit des Konzeptes kommt wohl vielen Anfängern entgegen, denn über das "Einfrieren"-Problem fällt wohl jeder mal.
Arbeitet man mit Studio entfällt zudem eine Menge händisch zu schreibenden Code. Der Nachteil der Lösung ist m.E., dass es eben nur für das GUI-Thread-Problem taugt und dem Nutzer keinen wirklich Einblick in das Threading erlaubt. Möchte man Threads anderweitig einsetzen steht man wieder vor dem gleichen Problem.
Ich habe es jetzt mit Threads gelöst, musste allerdings auch mit DoEvents arbeiten da es sonst wieder einfror.
Vielen dank für die Beiträge.
Das riecht danach, als hättest du das mit den Threads immer noch nicht richtig implementiert. Dann brauchst du nämlich kein DoEvents().
Ich mag es nicht wenn etwas riecht 😉
Thread[] th = new Thread[auswahlcount];
for (int i = 0; i < auswahlcount; i++)
{
// ....
th[i] = new Thread(new ThreadStart(picresize.resizePic));
th[i].Start();
// ....
Application.DoEvents();
}
Reicht das für eine eventuelle Fehlerdiagnose ?
Das Anstarten der Threads sollte nur ein paar ms dauern (es sei denn, da ist noch was in "// ..." oder viele Threads). Da ist kein DoEvents() notwendig.
Im Zweifelsfall das Starten der Threads in einen Thread auslagern. 🙂
Aber mal ehrlich: Hier ist der ThreadPool eigentlich besser. Bedenke, dass die Performance bei Multi-Threading nur mit der Zahl der (virtuellen) Prozessoren (HT, Multi-Proz oder Dual Core) skaliert.
Mehr Threads zu erzeugen (die auch wirklich parallel laufen), als du Prozessoren hast, steigert nicht den Durchsatz, im Gegenteil. Der Threadpool hat schon deswegen den Vorteil, weil die Kosten, die mit der Thread-Erzeugung einhergehen, entfallen, weil die Threads "wiederverwendet" werden. Eine ThreadPool-Implementierung sollte also bei weniger gleichzeitig aktiven Threads eine bessere Leistung bieten.
Ein File.Copy sowie 2 - 3 Zuweisungen sind da nur noch.
Liegt es evtl. am Filecopy ?
Also es friert selbst dann ein wenn der File.Copy Befehl weg ist.
Dann schau ich mal das ich das in nen Extra Thread packe.
Kleiner Tipp: Besorg die mal einen Profiler (den braucht man eh manchmal) und untersuche das Zeitverhalten der Schelife. Dann siehst du genau, wo die Zeit "verloren" geht.
FileCopy() ist natürlich tendenziell auch eine Operation für einen Thread. Das Kopieren einer großen Datei kann ja u.U. ein paar Sekunden dauern. In der Zeit steht dein GUI.
Nach rausnehmen der FileCopy Anweisung hängt nun fast der ganze PC für einige Minuten.
Der Speicherverbrauch geht in die 100e MB.
Kleiner Hinweis, das Programm verkleinert Bilder die ich in einer Listbox gelistet habe.
Zum testen nehme ich immer 100 Bilder von einer Digicam.
Ich glaube da hab ich noch fette Fehler drin 🙁
Doofe Frage, was ist ein Profiler ?
Hallo Mani,
Ich glaube da hab ich noch fette Fehler drin
Ich auch. Wie gesagt, wenn du alle langdauernden Operationen in einen Thread auslagerst, dann blockiert das GUI nicht.
was ist ein Profiler? http://de.wikipedia.org/wiki/Profiler
herbivore
Ich seh den Knackpunkt nicht 🙁
Hier mal die Threadklasse und der Button_Click aus der Form...
// Threadklasse
public class resize
{
private int maxbreite;
private string picture;
private string picture_neu;
public resize(int size, string pic1, string pic2)
{
maxbreite = size;
picture = pic1;
picture_neu = pic2;
}
public void resizePic()
{
Int32 iScaledWidth = new Int32();
Int32 iScaledHeight = new Int32();
System.Drawing.Image oImg = System.Drawing.Image.FromFile(picture);
if (oImg.Height > oImg.Width)
{
iScaledHeight = Convert.ToInt32(maxbreite);
iScaledWidth = (int)((double)((double)oImg.Width / (double)oImg.Height) * (double)iScaledHeight);
}
if (oImg.Height < oImg.Width)
{
iScaledWidth = Convert.ToInt32(maxbreite);
iScaledHeight = (int)((double)((double)oImg.Height / (double)oImg.Width) * (double)iScaledWidth);
}
System.Drawing.Image oScaledImage = new Bitmap(iScaledWidth, iScaledHeight, oImg.PixelFormat);
Graphics oGraphic = Graphics.FromImage(oScaledImage);
oGraphic.CompositingQuality = CompositingQuality.HighQuality;
oGraphic.SmoothingMode = SmoothingMode.HighQuality;
oGraphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
Rectangle oRectangle = new Rectangle(0, 0, iScaledWidth, iScaledHeight);
oGraphic.DrawImage(oImg, oRectangle);
oScaledImage.Save(picture_neu, System.Drawing.Imaging.ImageFormat.Jpeg);
oImg.Dispose();
}
}
// Buttonklick
private void startToolStripMenuItem_Click(object sender, EventArgs e)
{
// Zeiger in eine Sanduhr ändern
this.Cursor = System.Windows.Forms.Cursors.WaitCursor;
// Elemente sperren
menuStrip1.Enabled = false;
listBox1.Enabled = false;
checkBox1.Enabled = false;
groupBox1.Enabled = false;
groupBox2.Enabled = false;
pictureBox1.Enabled = false;
textBox1.Enabled = false;
statusStrip1.Enabled = false;
toolStripStatusLabel1.Enabled = false;
// Neues Verzeichnis benennen
string newpath = textBox1.Text + "\\" + DateTime.Now.Year.ToString() +
DateTime.Now.Month.ToString() + DateTime.Now.Day.ToString() +
DateTime.Now.Hour.ToString() + DateTime.Now.Minute.ToString() +
DateTime.Now.Second.ToString();
// Anzahl der ausgewählten Dateien
int auswahlcount = listBox1.SelectedItems.Count;
// Label setzen
label1.Text = "Bild: 0/" + auswahlcount;
// Minimumanzeige
progressBar1.Minimum = 1;
// Maximumanzeige
progressBar1.Maximum = listBox1.SelectedItems.Count;
// Startanzahl der Balken
progressBar1.Value = 1;
// Balken pro Schritt
progressBar1.Step = 1;
// überprüfen ob das Verzeichnis schon existiert
if (!Directory.Exists(newpath))
{
// neues Verzeichnis für die neuen Bilder erstellen
Directory.CreateDirectory(newpath);
}
// Neuer Thread wird gestartet
Thread[] th = new Thread[auswahlcount];
for (int i = 0; i < auswahlcount; i++)
{
if (!File.Exists(newpath + "\\" + listBox1.SelectedItems[i].ToString()))
{
// Hier soll die Kopie hin
string picture = textBox1.Text + "\\" + listBox1.SelectedItems[i].ToString();
// verkleinertes Bild
string picture_neu = newpath + "\\" + i + ".jpg";
// Kopiert das Bild in den neuen Ordner
//File.Copy(textBox1.Text + "\\" + listBox1.SelectedItems[i].ToString(), newpath + "\\" + listBox1.SelectedItems[i].ToString());
resize picresize = new resize(600, picture, picture_neu);
th[i] = new Thread(new ThreadStart(picresize.resizePic));
th[i].Start();
// Progressbar erhöhen
progressBar1.PerformStep();
// Label setzen
label1.Text = "Bild: " + (i+1) + "/" + auswahlcount;
Application.DoEvents();
}
else
{
//File.Copy(textBox1.Text + "\\" + listBox1.SelectedItems[i].ToString(), newpath + "\\new_" + listBox1.SelectedItems[i].ToString());
}
}
// Auswahl aufheben
listBox1.SelectedItems.Clear();
// Alle Funktionen aktivieren
menuStrip1.Enabled = true;
listBox1.Enabled = true;
checkBox1.Enabled = true;
groupBox1.Enabled = true;
groupBox2.Enabled = true;
pictureBox1.Enabled = true;
textBox1.Enabled = true;
statusStrip1.Enabled = true;
toolStripStatusLabel1.Enabled = true;
this.Cursor = System.Windows.Forms.Cursors.Default;
// Erfolgsmeldung bringen
MessageBox.Show(auswahlcount + " Bilder erfolgreich verkleinert und in " + newpath + " kopiert !", "Hinweis");
}
Erschlagt mich nicht gleich wegen dem Code, ich sitze an C# erst seit 9 Tagen dran 🙁
Hallo Mani,
grob gesehen, solltest du den Code von "Neues Verzeichnis benennen" bis vor "Auswahl aufheben" in eine eigene Methode packen und diese Methode in einem Thread starten. Die in diesem Code verbliebenen Zugriffe auf Controls musst du in jeweils eigene Methoden packen und diese Methoden per Control.BeginInvoke aufrufen.
DoEvents brauchst du dann nicht mehr.
Das mit dem Thread-Array ist unsinnig (nicht böse gemeint).
herbivore
Vielen Dank, ich werd mich dann mal dransetzen und es versuchen so umzusetzen.
Aller Anfang ist schwer, aber wer die Arbeit nicht scheut der wird den Anfang schaffen 🙂
Kleine Anfängerfrage noch dazu...
Wie kann ich aus der Threadklasse denn auf die Steuerelemente zugreifen ?
Er meckert bei textBox1 sowie den anderen auch das die im aktuellen Kontext nicht vorhanden sind.
Hallo Mani,
du brauchst m.E. keine ThreadKlasse, sondern kannst und solltest den Code einfach in deine Form-Klasse packen.
herbivore
Original von herbivore
Hallo Meli,was denn für BeginInvoke/EndInvoke/Delegate-Schwachsinn?
Mal abgesehen davon, dass du um Delegaten auch beim BackgroundWorker nicht drum rum kommst, ist ein BeginInvoke doch 'ne simple Sache: Statt eine Methode aufzurufen, bittet man BeginInvoke das zu tun. That's all. Ist genauso einen Zeile wie ReportProgress.
Dagegen braucht man zum Starten eines Threads 1-2 Zeilen. Zum Starten eines BackgroundWorkers mindestens 5.
herbivore
Sagen wir mal so: Ich hoffe das weder die Thread-Klasse, noch die Backgroundworker-Komponente der Weisheit letzter Schluß ist in Sachen Multithreading.Besonders was das parallelisieren von Algorithmen betrifft.
Siehe die OpenMP-API für C++
Ansonsten hast Du natürlich recht 👍
Meli
Puuh, das mit dem Invoke ist mir ein bisschen zu hart für heute.
Ich glaub ich setz da morgen früh nochmal an.
Dieses Threadübergreifende mit der listbox und den ganzen anderen Controls wird mich denke ich noch aufhalten.
Ich danke für die Hilfe bis hierher und werd mich hier nochmal melden wenn ich es geschafft habe (oder wenn ich am verzweifeln bin 🙂).
Ich bekomm das nicht auf die Reihe.
Ich habe nun alles an Code was Du gesagt hast in eine Methode Namens resizePic in der selben Klasse gelegt und die Threadklasse entfernt.
Bei der Ausführung des Programms meckert er dann nachdem ich auf Start gedrückt habe hier als erstes...
auswahlcount = listBox1.SelectedItems.Count;
Da sagt er mir das mit dem Threadübergreifend.
Wie wende ich da Begininvoke mit an ?
Das BeginInvoke muss ich doch in der Methode resizePic nutzen wenn ich das richtig verstanden habe, oder ?
Ok, ich muss noch mit delegate arbeiten, aber wie setze ich delegate in Bezug auf die listBox an ?
Sorry für meine dummen Fragen, aber trotz Forensuche, MSDN und wühlen in einem Buch komm ich nicht weiter.
Hallo Mani,
du kannst es genau so machen, wie ich geschrieben habe. Pack den Zugriff in eine Methode und rufe diese Methode mit BeginInvoke auf. Beispiele für BeginInvoke findest du im Forum.
herbivore
Soooo
Aller Anfang ist bekanntlich schwer 🙂
Ich habe Stunden, fast schon Tage gebraucht bis ich es in meinen Kopf bekam 🙂
Ich kann nicht sagen wie oft ich die Postings hier nochmal gelesen habe, aber sie haben mir definitiv die Lösung gebracht.
Ich hab alles in einer Klasse gelassen, für jedes bearbeiten der Controls, sei es auch nur das deaktivieren und aktivieren, eine Methode angelegt.
Eine Methode für die Verarbeitung angelegt.
Thread mit dieser Methode angelegt, BeginInvoke überall eingesetzt und es rennt wie es rennen soll.
Nachdem ich das mit dem BeginInvoke raus hatte war es eine Sache von paar Minuten 😉
Ich bedanke mich bei allen die mir geholfen haben.
Schön das es noch Foren gibt wo Anfänger die auch manchmal nervig werden so behandelt werden wie hier.
Daumen hoch dafür.
Und ihr werdet noch einiges von mir zu lesen bekommen 🙂