Laden...

Öffnen & Vorverarbeiten eines Bildes in einen extra Thread packen, um das GUI nicht zu blockieren?

Erstellt von el_vital vor 12 Jahren Letzter Beitrag vor 12 Jahren 6.923 Views
Thema geschlossen
E
el_vital Themenstarter:in
346 Beiträge seit 2007
vor 12 Jahren
Öffnen & Vorverarbeiten eines Bildes in einen extra Thread packen, um das GUI nicht zu blockieren?

Ich habe in meinem Programm eine Funktion bei der der Benutzer ein Bild von einem Speichermedium auswählt. Nach der Auswahl wird es vorverarbeitet und angezeigt.
Diese Aktion mit Öffnen des Bildes, Vorverarbeitung und anschließendem Refresh() der PictureBox wo das Bild angezeigt wird, dauert rund 2 Sekunden. In dieser Zeit zeige ich ein Wait-Cursor an.

Jetzt habe ich hier im Forum gelesen, dass man Aktionen die länger als 1/10s dauern in eine extra Thread auslagern sollte. Macht es in diesem Fall Sinn? Auch wenn der Benutzer in diesen 2 Sekunden sowieso nichts anderes machen kann?

296 Beiträge seit 2007
vor 12 Jahren

Hallo el_vital,

kurz und knackig: Ja, warum auch nicht?

6.911 Beiträge seit 2009
vor 12 Jahren

Hallo el_vital,

mMn macht es auch hier Sinn. Es ist besser wenn der User nix tun kann*, als dass die Anwendung einfriert.

* er könnte ESC drücken um den Ladevorgang abzubrechen. Das geht dann nur sinnvoll mit einem extra Thread.

Wenn du .net 4.0 hast, schau dir auch mal Pipelines an. Das passt genau dazu, wenns mehrere sind. 😉

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

1.552 Beiträge seit 2010
vor 12 Jahren

Hallo el_vital,

klar macht es Sinn. Auch wenn der User nichts machen kann friert trotzdem die GUI ein d.h. klickt der User wild rum kommt ein tolles "Anwendung reagiert nicht mehr" Bildchen. Und m.e. sollten diese Bildchen so gut wie möglich vermieden werden.

Gruß
Michael

Mein Blog
Meine WPF-Druckbibliothek: auf Wordpress, myCSharp

107 Beiträge seit 2011
vor 12 Jahren

Das, was du vor hast, bezeichnet man auch als "Image-Proxy-Pattern".

q.e.d.

6.911 Beiträge seit 2009
vor 12 Jahren

Hallo ProgrammierTroll,

ich sehe in seinem Vorhaben keinen Bezug zum Proxy-Pattern. Das Image-Proxy-Pattern kenn ich nicht und gefunden hab ich darüber auch nix, aber ich denke dass es das nicht ist was er sucht.

Das Pattern das er verwendet ist [FAQ] Warum blockiert mein GUI? 😃

Aber lass uns nicht abschweifen. Bleiben wir beim ursprünglichen Thema.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

107 Beiträge seit 2011
vor 12 Jahren

Es ist ein virtueller Proxy, der einen Stellvertreter anzeigt, bis das richtige Bild geladen ist. Dazu wird ein Thread eröffnet und das Bild nach Abschluss des Ladens an eine entsprechende Zeichenmethode deligiert. Image-Proxy

Ich dachte, das wäre, was er sucht.

q.e.d.

1.552 Beiträge seit 2010
vor 12 Jahren

Ich dachte, das wäre, was er sucht.

Jein. Dies ist eine Möglichkeit dem User anzuzeigen dass das Bild noch nicht anzeigebereit ist. Jedoch war Sinn und Zweck der Frage ob die Bildbearbeitung in einen separaten Thread ausgelagert werden sollte oder lieber im GUI Thread bleiben soll.

Mein Blog
Meine WPF-Druckbibliothek: auf Wordpress, myCSharp

107 Beiträge seit 2011
vor 12 Jahren

Ja, und es zeigt, wie die Aufgabe des Ladens der aufwendigeren Ressource abgwickelt werden kann (nämlich einem separaten Thread). Gut, es ist in dem Fall wohl nicht von einer Netzwerk-Ressource die Rede, aber das sollte man nicht so dogmatisch sehen.

Wenn es schon so ein Pattern gibt, dann sehe ich eigentlich keinen Grund, es nicht für sich zu nutzen.

q.e.d.

E
el_vital Themenstarter:in
346 Beiträge seit 2007
vor 12 Jahren

Ich danke euch für die Meinungen. Ganz tief im Inneren weiß ich, dass es sinnvoll und sauber ist solch eine Aufgabe in einen eigenen Thread auszulagern. Jedoch sperrt sich meine Faulheit und mein geringes Wissen in diesem Bereich das auch zu tun.

Wenn ich mal mehr zeit habe werde ich es so umsetzen. Das Problem ist, dass der Code in dem jetzigen Zustand nicht direkt in ein Thread auszulagern ist. Es ist nicht sauber programmiert und es gibt im Ablauf zu viele Zugriffe auf die GUI.

E
el_vital Themenstarter:in
346 Beiträge seit 2007
vor 12 Jahren

WOW, ich bin begeistert. Ich habe die Funktion in ein extra Thread ausgelagert und nicht nur das ich jetzt bequem eine "Bitte warten" Animation anzeigen kann, die Abarbeitung ist fast doppelt so schnell. In dem GUI Thread haben also Timer und verschiedenen Events die Abarbeitung zwischen durch gestört.

Danke für den Schubs in die richtige Richtung!

E
el_vital Themenstarter:in
346 Beiträge seit 2007
vor 12 Jahren

Eine Frage habe ich noch. In meinem GUI Thread lasse ich jetzt eine Animation laufen. Es läuft auch flüssig bis in einem zweiten Thread folgendes passiert:


    Bitmap btmTemp = new Bitmap(BreiteNeuTemp, HoeheNeuTemp);
                    Graphics gf = Graphics.FromImage(btmTemp);
                    
                    gf.CompositingQuality = CompositingQuality.HighQuality;
                    gf.InterpolationMode = InterpolationMode.HighQualityBicubic;
                    gf.SmoothingMode = SmoothingMode.HighQuality;
                    gf.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
                    

                    gf.DrawImage(btmUebergabe, new Rectangle(0, 0, BreiteNeuTemp, HoeheNeuTemp), new Rectangle(0, 0, btmUebergabe.Width, btmUebergabe.Height), GraphicsUnit.Pixel);
                  

Während der Operation gf.DrawImage stockt meine Animation in dem Haupt-Thread. Wie kann es passieren? Die CPU Kerne sind nur leicht ausgelastet und es sollte die GUI doch überhaupt nicht beeinflussen?

Hinweis von winSharp93 vor 12 Jahren

Antwort aus [erledigt] DrawImage blockiert GUI Thread entfernt, da diese offensichtlich genau das gleiche Problem behandelt.

E
el_vital Themenstarter:in
346 Beiträge seit 2007
vor 12 Jahren

herbivore hat mich netterweise gebeten ein Mini-Projekt anzuhängen, welches das Verhalten vorführt. (Trotz: 4.1 Bitte keine kompletten Projekte anhängen)

Hier ist es nun. Ich hoffe immer noch für dieses Problem eine Lösung zu finden.

49.485 Beiträge seit 2005
vor 12 Jahren

Hallo el_vital,

tja, sieht schlecht aus.

Um sicher zu gehen, dass es wirklich (nur) das DrawImage ist, das die Blockierung auslöst, habe ich in den Worker-Thread noch eine leere for-(Warte-)Schleife davor und dahinter eingefügt. Die Schleifen laufen wie erwartet ohne das GUI zu stören, es ist tatsächlich nur das DrawImage, das das GUI blockiert. Anschließend habe ich je ein Fenster in zwei verschiedenen GUI-Threads gestartet, eins direkt aus dem Main, eins aus einem zweiten, extra gestarteten GUI-Thread (jeder GUI-Thread hat natürlich sein eigenes Application.Run ausgeführt). Egal aus welchem der Fenster man den Worker-Thread startet, beide Fenster blockieren. Es sieht also so aus, als würde DrawImage absichtlich(?) alle im selben Prozess vorhandenen Nachrichtenschleifen anhalten.

Daraus ergeben sich für mich zwei Möglichkeiten: Auf DrawImage verzichten bzw. durch andere Operationen ersetzen oder die Verarbeitung aus dem Worker-Thread in einen anderen Prozess auslagern.

herbivore

5.742 Beiträge seit 2007
vor 12 Jahren
E
el_vital Themenstarter:in
346 Beiträge seit 2007
vor 12 Jahren

Vielen Dank herbivore für deine Versuche.
Ich überlege das Zeichnen in kleine Schritte zu unterteilen.
Ich könnte das eine Bild auch selbst unsafe auf das andere kopieren und dabei Pixel auslassen. Es wäre auch verkleinert, allerdings ohne irgend einer Interpolation.

E
el_vital Themenstarter:in
346 Beiträge seit 2007
vor 12 Jahren

Was meint ihr zu der Lösung das Bild in einer Windows-Form-Anwendung intern mit WPF zu laden, zu verkleinern und über ein ByteArray zurück in ein Bitmap-Objekt zu konvertieren und wie gewohnt weiter zu verwenden? Spricht etwas dagegen?

5.742 Beiträge seit 2007
vor 12 Jahren

Spricht etwas dagegen?

Wurde ja auch in dem von mir verlinkten Thread vorgeschlagen 😉

Nein, da spricht nichts dagegen - vorausgesetzt, dir steht .NET 3.0 aufwärts zur Verfügung.

E
el_vital Themenstarter:in
346 Beiträge seit 2007
vor 12 Jahren

winSharp93, das habe ich leider übersehen.

Ich habe es gerade ausprobiert. Grundsätzlich ist es möglich und gar nicht mal schlecht, (aber es werden Ressourcen nicht frei gegeben und es entsteht ein Speicherleck. Ich sehe nicht wo.

Bei diesem Code geht bereits Speicher verloren, ohne dass ich daraus ein ByteArray und daraus wieder eine Bitmap erstelle. Muss ich noch etwas frei geben?


  string FileName = "Test.jpg";
            byte[] Source = File.ReadAllBytes(FileName);
            Stream stream = new MemoryStream(Source);
            BitmapFrame Photo = ReadBitmapFrame(stream);
            
            ScaleTransform st = new ScaleTransform(0.5, 0.5, 0, 0);

            TransformedBitmap transformedBitmap = new TransformedBitmap(Photo, st);

            BitmapFrame Thumb = BitmapFrame.Create(transformedBitmap);

            stream.Close();
            stream.Dispose();

5.742 Beiträge seit 2007
vor 12 Jahren

aber es werden Ressourcen nicht frei gegeben und es entsteht ein Speicherleck.

Wie hast du das Speicherleck "entdeckt" bzw. woran erkennst du es?

BTW: Einen MemoryStream aus dem Rückgabewert von File.ReadAllBytes zu erstellen ist eher suboptimal 😉

E
el_vital Themenstarter:in
346 Beiträge seit 2007
vor 12 Jahren

Wie hast du das Speicherleck "entdeckt" bzw. woran erkennst du es?

Bezüglich des Speicherlecks habe ich mich wohl getäuscht. Der Speicherverbrauch steigt nur einmal und wird nicht frei gegeben, wenn ich aber mehrere Operationen durchführe steigt es nicht weiter an.

Was ist die Alternative zu dem hier?


byte[] Source = File.ReadAllBytes(FileName);
            Stream stream = new MemoryStream(Source);
            BitmapFrame Photo = ReadBitmapFrame(stream);

6.911 Beiträge seit 2009
vor 12 Jahren

Hallo el_vital,

ein Stream kann auch ein FileStream sein der mit File.Read od. new FileStream erzeugt wird. Bitte beachte [Hinweis] Wie poste ich richtig? Punkt 1.1.1.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

E
el_vital Themenstarter:in
346 Beiträge seit 2007
vor 12 Jahren

Hier ist mein Code, falls es mal jemand braucht. Es funktioniert jetzt wie ich es mir gewünscht hatte. Danke nochmal an alle.


public static Bitmap LoadBildVerkleinertWPF(string sPfad, int iMaxPixel)
        {
            FileStream myFileStream = File.Open(sPfad, FileMode.Open, FileAccess.Read);
            BitmapFrame Photo = ReadBitmapFrame(myFileStream);

            double WidthNew, HeightNew;
            double dVerhaeltnis = 1;
            if (Photo.PixelWidth > Photo.PixelHeight)
            {
                WidthNew = iMaxPixel;
                HeightNew = (double)Photo.Height * (double)iMaxPixel / (double)Photo.Width;

                dVerhaeltnis = ((WidthNew * 100f) / Photo.PixelWidth) / 100f; ;
            }
            else
            {
                HeightNew = iMaxPixel;
                WidthNew = (double)Photo.Width * (double)iMaxPixel / (double)Photo.Height;
                double EinPro = Photo.PixelHeight / 100f;
                dVerhaeltnis = (HeightNew / EinPro) / 100f;
            }
			
            ScaleTransform st = new ScaleTransform(dVerhaeltnis, dVerhaeltnis, 0, 0);

            TransformedBitmap transformedBitmap = new TransformedBitmap(Photo, st);

            BitmapFrame Thumb = BitmapFrame.Create(transformedBitmap);

            byte[] baResize = ToByteArray(Thumb);

            ImageConverter ic = new ImageConverter();
            Image img = (Image)ic.ConvertFrom(baResize);
            Bitmap bitmap = new Bitmap(img);
            bitmap.SetResolution(350, 350);

            myFileStream.Close();
            myFileStream.Dispose();
            img.Dispose();
            GC.Collect();
            return bitmap;
        }

6.911 Beiträge seit 2009
vor 12 Jahren

Hallo el_vital,

als Hinweise zum Code (nur das wichtigste): Lass GC.Collect weg, der GC soll selber aktiv werden wenn es seine Heuristik sagt - damit bringst du in nur aus "der Ruhe" und das wirkt sich i.d.R. sogar negativ aus und es bringt auch nix wenn du Dispose aufrufst (und das Dispose-Pattern bei den Klassen korrekt implementiert ist, wovon ausgegangen werden kann). Verwende auch besser using, denn das entspricht try-finally wobei im finally das Dispose aufgerufen wird.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

E
el_vital Themenstarter:in
346 Beiträge seit 2007
vor 12 Jahren

Hallo gfoidl,

danke für den Hinweis. Ich halte den Aufruf von GC.Collect() für Glaubensfrage. Ich persönlich finde es gut die 100MB Speicher, die ein sehr großes Bild bei dieser Funktion verbraucht, unverzüglich frei zu geben. Ich kann es nicht mit ansehen wenn bei Bearbeitung von einigen großen bitmaps der Speicherverbrauch meines Programms auf 500MB und mehr ansteigt. Nach einem GC.Collect() sind es nur noch 90MB.

5.742 Beiträge seit 2007
vor 12 Jahren

Ich halte den Aufruf von GC.Collect() für Glaubensfrage.

Nein, in dem Fall ist das wirklich deutlich übertrieben bzw. nicht in dem Umfang notwendig.
Eine vollständige Garbagecollection über alle Generationen ist an dieser Stelle vollkommen sinnlos und bremst das Programm nur unnötig aus.

Wenn du tatsächlich User hast, die auf ein Programm mit geringem Speicherabdruck wert legen, kannst du immernoch GC.Collect zyklisch in einem Timer aufrufen (z.B. alle 5 Minuten einmal).

E
el_vital Themenstarter:in
346 Beiträge seit 2007
vor 12 Jahren

An einen timer habe ich auch bereits gedacht. Allerdings mit einem Interval von 30 Sekunden. Das ist wahrscheinlich auch übertrieben und störend.

5.742 Beiträge seit 2007
vor 12 Jahren

Das ist wahrscheinlich auch übertrieben und störend.

Aber auf jeden Fall nicht so kritisch wie am Ende der Methode - wenn du beispielsweise auf diese Art 100 Bilder verkleinerst, hast du innerhalb weniger (oder sogar einer) Sekunden auch 100 Garbagecollections - und das ist nicht unbedingt förderlich für die Performance.
Dann blockiert zwar DrawImage nicht mehr, aber dafür GC.Collect 😉

Den Interval des Timers kannst du ja auch dynamisch anpassen; 30 Sekunden halte ich beispielsweise für deutlich zu kurz.

Hinweis von herbivore vor 12 Jahren

Womit wir jetzt endgültig meilenweit entfernt vom eigentlichen Thema angekommen sind.

Thema geschlossen