Autor |
Beitrag |
Gausi
      
Beiträge: 8549
Erhaltene Danke: 478
Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
|
Verfasst: Mo 21.12.09 23:06
Ich habe da grade ein kleines Problem mit TPicture oder TGraphic. Kann es sein, dass ich das nicht in einem Thread verwenden darf?
Bei mir murkst dieses Konstrukt (Code unten) ständig rum, wenn ich es in einem Thread ausführe.
Grobe Beschreibung, was das Ding machen soll: Eine Bilddatei laden, und die schön gestreckt auf maximal 240x240 in guter Jpeg-Qualität speichern. Dazu packe ich das geladene Graphic-Objekt erst in ein BigBmp, schaue mir die Maße an, berechne daraus einen Stretchfaktor, passe die Dimensionen von SmallBmp an und schreibe das in ein Jpeg.
Wenn das im VCL-Thread läuft: Alles ok.
Wenn es in einem Nebenthread läuft: Bei bestimmten Bildern reproduzierbar "Out of ressources", und einmal auch "Canvas erlaubt kein Zeichnen" (nicht reproduzierbar). Speicherauslastung laut Taskmanager: Voll im grünen Bereich (unter 100mb), die Bilder haben alle maximal eine Größe von 1000x1000, einige vielleicht etwas darüber, insgesamt wird das aktuell ca. 40 mal ausgeführt.
Kann mir das jemand erklären?
Ok, da fehlen ein paar Schutzblöcke, aber auch mit denen klappt es nicht.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69:
| LoadGraphicFromFile(aFileName, aGraphic); if Assigned(aGraphic) then begin if SafeResizedGraphic(aGraphic, CoverSavePath + newID + '.jpg', 240, 240) then result := newID; aGraphic.Free; end
function SafeResizedGraphic(aGraphic: TGraphic; dest: WideString; W,h: Integer; OverWrite: Boolean = False): boolean; var BigBmp, SmallBmp: TBitmap; xfactor, yfactor:double; aJpg: tJpegImage; aStream: TTntFileStream; begin result := True; if Not Overwrite and WideFileExists(dest) then exit; BigBmp := TBitmap.Create; SmallBmp := TBitmap.Create;
try BigBmp.Assign(aGraphic);
SmallBmp.Width := W; SmallBmp.Height := H;
if aGraphic <> NIL then begin if (aGraphic.Width = 0) or (aGraphic.Height=0) then exit; xfactor:= (W) / aGraphic.Width; yfactor:= (H) / aGraphic.Height; if xfactor > yfactor then begin SmallBmp.Width := round(aGraphic.Width * yfactor); SmallBmp.Height := round(aGraphic.Height * yfactor); end else begin SmallBmp.Width := round(aGraphic.Width * xfactor); SmallBmp.Height := round(aGraphic.Height * xfactor); end;
SetStretchBltMode(SmallBmp.Canvas.Handle, HALFTONE); StretchBlt(SmallBmp.Canvas.Handle, 0 ,0, SmallBmp.Width, SmallBmp.Height, BigBmp.Canvas.Handle, 0, 0, BigBmp.Width, BigBmp.Height, SRCCopy); aJpg := TJpegImage.Create; aJpg.CompressionQuality := 90; aJpg.Assign(Smallbmp); try aStream := TTntFileStream.Create(dest, fmCreate or fmOpenWrite); try aJpg.SaveToStream(aStream); finally aStream.free; end; except end;
aJpg.Free; end; except if WideFileExists(dest) then WideDeleteFile(dest); result := False; end; SmallBmp.Free; BigBmp.Free; end; |
_________________ We are, we were and will not be.
|
|
BenBE
      
Beiträge: 8721
Erhaltene Danke: 191
Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
|
Verfasst: Mo 21.12.09 23:09
Die VCL ist insgesamt eigentlich nicht als Threadsafe anzusehen, von daher verwundert mich dieses Problem eher weniger.
Es gibt nur recht wenige Klassen, die halbwegs als "in Threads verwendbar" anzusehen sind. Darunter z.B. die TString-Klassen und die meisten TStream-Implementierungen. Für die meisten Grafik-Klassen würd ich diese Eigenschaft aber absprechen.
_________________ Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
|
|
Gausi 
      
Beiträge: 8549
Erhaltene Danke: 478
Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
|
Verfasst: Mo 21.12.09 23:14
Grml. Ich habs ja fast befürchtet.
Naja, dann hat Nemp 3.9, was am Donnerstag oder so rauskommt, halt noch ein kleines Fehlerchen. Der macht sich nur manchmal bemerkbar, und dieses "manchmal" habe ich grade gefunden. Denn meistens wird dieser Code im Hauptthread ausgeführt.
Andere mögliche Fehlerquellen werden aber trotzdem gerne akzeptiert. 
_________________ We are, we were and will not be.
|
|
Flamefire
      
Beiträge: 1207
Erhaltene Danke: 31
Win 10
Delphi 2009 Pro, C++ (Visual Studio)
|
Verfasst: Mo 21.12.09 23:28
Hmm...
Woran liegen diese Fehler überhaupt? Wenn alles lokale Variablen sind, dürfte es doch in Neben-Threads keine Probleme geben, oder?
Die Bildbearbeitungsfunktionen, die du aufrufst, dürften ja nur Wrapper der WinAPI sein.
Wo kommt denn der obere Teil her, und in welcher Zeile tritt der Fehler auf?
|
|
Gausi 
      
Beiträge: 8549
Erhaltene Danke: 478
Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
|
Verfasst: Mo 21.12.09 23:43
Ja, es sind alles "lokale" Variablen. Der obere Teil ist aus einer anderen Prozedur, die auch im Kontext des Nebenthreads läuft. D.h. Das Laden des Bildes und das verkleinern läuft alles im Nebenthread ab. Der Hauptthread hat damit nichts zu tun. Evtl. ist Threadsafe hier der falsche Begriff. Ich meine damit "kann man in einem Thread benutzen".
Der Debugger bleibt in der Zeile hängen, wo die SafeResizedGraphic-Funktion aufgerufen wird. Wo genau, lässt sich schwer sagen. Durchsteppen bringt wenig, da der Fehler ja nur manchmal auftritt. Seltsam ist auch, dass ab und zu das Ergebnis verfälscht wird. D.h. wenn bei einem Bild, das meist einen Fehler verursacht, es dann doch durchläuft, dann sieht es so aus, als wäre die Zeile
SetStretchBltMode(SmallBmp.Canvas.Handle, HALFTONE); ignoriert worden. Das kleine Bild sieht dann sehr dunkel aus.
Alles in allem deutet viel auf ein Thread-Problem hin, nur verstehe ich nicht so ganz, was da schief läuft. 
_________________ We are, we were and will not be.
|
|
Flamefire
      
Beiträge: 1207
Erhaltene Danke: 31
Win 10
Delphi 2009 Pro, C++ (Visual Studio)
|
Verfasst: Mo 21.12.09 23:51
Du hast geschrieben, dass der eine Fehler reproduzierbar ist. Dann setz doch in jede Zeile mal einen Log-Breakproint,
dann weißt du, welche es ist.
Oder mach eine einfach Log-Ausgabe nach jeder Zeile. Über die letzte Ausgabe kannst du dann die Zeile finden.
Ansonsten mal die SetStretchBltMode angucken, was die noch macht.
|
|
Martok
      
Beiträge: 3661
Erhaltene Danke: 604
Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
|
Verfasst: Di 22.12.09 02:04
Deckt sich vollständig mit meine beobachtungen. Besonders TCanvas ist alles andere als ThreadSafe. Selbst wenn man die mit CriticalSections o.ä. absichert, das stört den nicht weiter: ich vermute ja fast schon einen Bug in Windows, der mit Threadkontext-wechseln und HDC Probleme bereitet.
Dass das Canvas nur aus einem Thread benutzt wird, scheint da wenig Einfluss zu haben. Bei mir hat sich das so geäußert, das ein zum BackBuffern genutztes TBitmap "spontan" leer geworden ist. Keine Fehler allerdings, es hat dann nur beim .Draw eine leere Fläche erzeugt; wie wenn das Handle ungültig geworden wäre.
_________________ "The phoenix's price isn't inevitable. It's not part of some deep balance built into the universe. It's just the parts of the game where you haven't figured out yet how to cheat."
|
|
BenBE
      
Beiträge: 8721
Erhaltene Danke: 191
Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
|
Verfasst: Di 22.12.09 02:29
Es gibt eine Reihe von Resourcen unter Windows die nur Thread-Lokal abgelegt werden. Insbesondere beim Zugriff auf Grafikkontexte scheint es da einige Dinge zu beachten zu geben. Bei OpenGL heißt es z.B. ausdrücklich nur innerhalb des gleichen Threads nutzen, weil OpenGL da einiges auf die TLS (Thread Local Storage) packt. Ob die normale GDI das auch macht, weiß ich nicht, aber ich denk mal schon.
Und etwas "lokal" mit CS abzustecken, wenn es intern nicht rund läuft, bringt oft recht wenig.
@Gausi: Für Nemp könntest du ggf. versuchen, inwiefern Du mit Fibern da bissl aufdröseln kannst. Sprich in den Threads nur vorbereiten und dann über Fibers die Aufgaben "parallel" zum Hauptthread abarbeiten. Da Fibers nicht mittendrin umschalten, sondern nur wenn angefordert, dürften damit auch nicht-thread-fähige Funktionen klarkommen.
Aber weil du StretchBlt ansprichst: Das Teil hat eh ne Reihe von Bugs. Starke Verkleinerungen wirken da meiner Erfahrung nach meist zu ner Verdunklung hin; auch bereits ohne Threads.
_________________ Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
|
|
Gausi 
      
Beiträge: 8549
Erhaltene Danke: 478
Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
|
Verfasst: Di 22.12.09 12:20
Zu den starken Verdunkelungen: Das bekommt man mit SetStretchBltMode(SmallBmp.Canvas.Handle, HALFTONE); geregelt. Dann sind die Ergebnisse deutlich besser.
Der Fehler tritt scheinbar immer in der Zeile aJpg.SaveToStream(aStream); auf.
Mit Fibers werde ich da aber nicht arbeiten. Ich brauche diesen Code halt für die Beschaffung der Cover und Abspeicherung einer kleinen Kopie im Nemp-Coverordner. Wenn nach neuen Dateien gesucht wird, läuft der Teil im Hauptthread (Heikos Searchtool schickt ne Message "neue Datei", die dann vom Hauptthread untersucht wird).
Wenn die Bib komplett aktualisiert wird, läuft alles im Nebenthread. Das werde ich wohl mal ändern. Ich schau mal, was da besser läuft: komplett im VCL-Thread mit ein paar APMs oder Nebenthread mit Messages an den Hauptthread. 
_________________ We are, we were and will not be.
|
|
matze
      
Beiträge: 4613
Erhaltene Danke: 24
XP home, prof
Delphi 2009 Prof,
|
Verfasst: Di 22.12.09 13:04
Mal ne andere Idee: Es gibt doch die GraphicEx Lib von Mike Lieschke.
In der sind ja auch einige Alogs implementiert, die man für die Größenumrechnung verwenden kann (die sogar bessere Ergebnisse bringen).
Vielleicht sind die ja Thread-Safe bzw Thread-useable ?
_________________ In the beginning was the word.
And the word was content-type: text/plain.
|
|
Gausi 
      
Beiträge: 8549
Erhaltene Danke: 478
Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
|
Verfasst: Di 22.12.09 19:15
_________________ We are, we were and will not be.
|
|
platzwart
      
Beiträge: 1054
Erhaltene Danke: 78
Win 7, Ubuntu 9.10
Delphi 2007 Pro, C++, Qt
|
Verfasst: Di 22.12.09 20:30
Gausi hat folgendes geschrieben : |
Der Fehler tritt scheinbar immer in der Zeile aJpg.SaveToStream(aStream); auf.
|
Exakt das gleiche Problem hatte ich ebenfalls. Beim Speichern von Bildern bei Multithreading traten seltsame Effekte auf.
_________________ Wissenschaft schafft Wissenschaft, denn Wissenschaft ist Wissenschaft, die mit Wissen und Schaffen Wissen schafft. (myself)
|
|
dummzeuch
      
Beiträge: 593
Erhaltene Danke: 5
Delphi 5 ent, Delphi 6 bis Delphi XE8 pro
|
Verfasst: Di 22.12.09 21:23
Gausi hat folgendes geschrieben : | Ich habe da grade ein kleines Problem mit TPicture oder TGraphic. Kann es sein, dass ich das nicht in einem Thread verwenden darf?
|
Also zumindest TBitmap und TJpeg sind threadsafe. Ich benutze beides in diversen Programmen, bei denen ein TBitmap von einem Thread gefuellt und dann an einen anderen zum Abspeichern als JPEG uebergeben wird. Das Problem ist nur, dass man mittels Canvas.Lock / .Unlock dafuer sorgen muss, dass einem Windows nicht die Handles unter dem Hintern wegzieht.
Ob das mit TPicture und TGraphic auch funktioniert, kann ich allerdings nicht sagen.
Zitat: |
Grobe Beschreibung, was das Ding machen soll: Eine Bilddatei laden, und die schön gestreckt auf maximal 240x240 in guter Jpeg-Qualität speichern. Dazu packe ich das geladene Graphic-Objekt erst in ein BigBmp, schaue mir die Maße an, berechne daraus einen Stretchfaktor, passe die Dimensionen von SmallBmp an und schreibe das in ein Jpeg.
|
Das ist so ziemlich genau das, was eines meiner Programme auch macht. Das sollte gehen.
twm
|
|
|