![]() |
[Fmx, Vcl] Verhalten von TBitmap in Threads
Hallo zusammen,
ich habe mal generellen Klärungsbedarf: Welche Operationen darf man unten allen Platformen mit einem Bitmap im Thread machen ? VCL: Unter VCL verstehe ich das so das Kopieren, Malen über den Bitmap.Canvas im Thread generell möglich ist. Lediglich das Übergeben an ein Control zum Anzeigen muss im UI-Thread passieren. FMX: Hier vermute ich mal das Bitmap.Canvas auch eines rein im Speicher angelegten Bitmaps schon mit UI zu tun hat. Womöglich wegen des Canvas, der unter FMX in verschiedenen Ausführungen erscheint, je nach Plattform. Ich würde gerne mal klären welche Operation grundsätzlich auf allen Platformen OK sind, und welche nicht:
Delphi-Quellcode:
Habe ich was vergessen, kann man was ergänzen ?
---------------- UI-Komponente <-- zu ---> Speicher-Bitmap im Thread
01. Create --------# im Thread bmpThread.Create(100, 100); ---- Ok: VCL-Ja, Fmx-? 02. Clear ---------# im Thread bmpThread.Clear(0); ------------ Ok: VCL-Ja, Fmx-? 03. Bearbeiten ----# im Thread bmpThread.Canvas.Draw... ------- Ok: VCL-Ja, Fmx-? -mit oder ohne Map/UnMap ? 04. Kopieren ------# im Thread bmpThread.Assign( bmpThread2); - Ok: VCL-Ja, Fmx-? 05. Filelesen -----> im Thread bmpThread.LoadFromFile(); ------ ?? 06. Fileschreiben -> im Thread bmpThread.SaveToFile(); -------- ?? 07. Streamlesen ---> im Thread bmpThread.LoadFromStream(); ---- ?? 08. Streamschreib. > im Thread bmpThread.SaveToStream(); ------ ?? 09. Assign --------> im Thread bmpThread.Assign( bmpUI ); ----- Nicht OK, muss syncronisiert werden 10. Zurückgeben ---< im Thread bmpUI.Assign( bmpThread ); ----- Nicht OK, muss syncronisiert werden Wie verhält sich das unter Fmx, so das es optimal unter allen Platformen läuft ? Ich würde gerne alles bis auf 09/10 im Thread ohne grosse Verrenkungen benutzen, ist das denkbar ? Vielleicht könnte man eine TBitmapThreadSafe Klasse bauen, das dies alles möglichst kapseln würde wenn man die im Thread benutzt ? Wäre schön wenn sich damit jemand auskennen und mir auf die Sprünge helfen könnte. Rollo |
AW: [Fmx, Vcl] Verhalten von TBitmap in Threads
Der Zugriff auf den Canvas bringt das non-thread-safe.
Die
Delphi-Quellcode:
Klasse ist nur für die Verwendung im MainThread gedacht gewesen und sollte daher auch nur in diesem Kontext verwendet werden.
TBitmap
Wenn jemand das anders verwendet und es funktioniert, dann diese Code-Zeilen in das Nachgebet einschliessen (auf das es weiter funktioniert). Zugesichert wird die Funktion in keiner Weise. |
AW: [Fmx, Vcl] Verhalten von TBitmap in Threads
Das stimmt für die VCL, und dann auch nur bei Zugriffen über Canvas. So lange ich via Scanline in einem TBitmap rumfuchtel sollte alles okay sein.
Aber: Ist das bei FMX noch genau so? Ich weiss es mangels Delphi-Version mit FMX nicht*, aber FMX arbeitet gerade beim Thema Grafik explizit nicht mit der WinAPI, welche die Schnittstelle zum Canvas bereitstellt. Von daher kann hier alles anders aussehen. Eine offizielle oder zumindest gut informierte und begründete Aussage wäre da in der Tat wünschenswert. *) Und ich bin bei Bitmaps komplett auf die Graphics32 umgestiegen, welche in Threads keinerlei Probleme bereitet. |
AW: [Fmx, Vcl] Verhalten von TBitmap in Threads
Zitat:
![]() |
AW: [Fmx, Vcl] Verhalten von TBitmap in Threads
Remmy Lebeau hat es mal bezogen auf das VCL-TBitmap zutreffend so beschrieben:
"Technically, nothing about it is thread-safe. You should always provide a lock around multi-threaded access to shared resources. But as long as neither bitmap is ever being resized or having its underlying handles regenerated, in other thread while you are reading the Scanline[property] or modifying its contents, then you should be ok." Bei FMX ist es im Prinzip ähnlich, Bitmap / Canvas ist auch nicht Threadsafe, das oben gesagte gilt aber auch hier. Zusätzlich helfen Dir aber BeginScene und Endscene die Zugriffe auf den Canvas Kollisionsfrei hinzubekommen (viele nutzen allerdings Beginscene quasi als Procedur-Aufruf und werten das Ergebnis nicht aus) bzw. Map und Unmap beim Zugriff auf die Pixeldaten des Bitmaps. Insofern macht so eine Auflistung hier m.E., wenig Sinn, hängt alles vom Kontext Deines Threads bzw. der evtl. gemeinsam benutzten Ressourcen ab. |
AW: [Fmx, Vcl] Verhalten von TBitmap in Threads
Ja, ach. Ole ole. Dass man ein und dieselbe Ressource nicht in 2 Threads gleichzeitig verändern darf ist jetzt aber auch nicht gerade eine exklusive Domäne von TBitmap, oder? Das selbe gilt ebenso für TList, Arrays, und jeden anderen Typen, bei dem Manipulation nicht in atomaren Operationen geschieht, bzw. wo Referenzen existieren die ggf. bei "fremden" Akteuren nachgeführt werden müssen.
Bei Bitmaps kommt (unter Win32 zumindest!) lediglich dazu, dass hier u.U. Handles vom OS geändert werden, die ein Thread möööglicherweise mal irgendwo gepuffert hat. (Lies: Ich übergebe einem Thread ein HBITMAP oder einen Canvas-Handle statt der Referenz auf das TBitmap-Objekt.) Aber genau diese 2 Dinge existieren unter FMX möglicherweise gar nicht. Diese Quellen alleine sind grob simplifizierend, und wenden eine eigentlich sehr allgemeine Regel unberechtigt spezifisch auf TBitmap an. Dass man bei Threads und gemeinsamem Zugriff auf was auch immer eine Form von Synchronisation braucht sollte wohl jedem klar sein, der Threads 2-3 Mal in der Hand hatte. Spätestens. |
AW: [Fmx, Vcl] Verhalten von TBitmap in Threads
Dankesehr erstmal für die Antworten.
Zitat:
Zumindest für die Basics. Ich habe grosse Unterschiede zw. iOS und Android, OSX und Win genauso. Ich möchte aber einen Source für Alles benutzen können, und suche nach Möglichkeiten das zu kapseln. @Harry Der Hinweis mit Scanline ist sehr gut, ich bin bisher davon ausgegangen das es immer auch mit dem Canvas zu tun hat. Aber es scheint wirklich nur den Speicher anzufassen. Vielleicht ist das ein Ansatz zumindest Bitmaps 1:1 zu kopieren, um von einem Bitmap z.B. an ein TListView Image weiterzugeben. Immerhin. Aber ich vermute das TBitmap.Create() im Hauptprozess wird auch schon einiges an Performance brauchen, und das Kopieren am Ende ist dann nur ein Teil.
Delphi-Quellcode:
@Meduim
function TBitmapData.GetPixelAddr(const I, J: Integer): Pointer;
begin Result := Pointer(NativeInt(GetScanline(J)) + I * BytesPerPixel); end; function TBitmapData.GetScanline(const I: Integer): Pointer; begin Result := Pointer(NativeInt(Data) + I * Pitch); end; procedure TBitmapData.Copy(const Source: TBitmapData); var I: Integer; begin for I := 0 to Height - 1 do Move(Source.GetScanline(I)^, GetScanline(I)^, BytesPerLine); end; +1 für Graphics32: Nur gibt es das ja leider (noch) nicht für Fmx. Unter VCL benutze ich das auch ohne Probleme. @Sir Rufo Wenn man niemals einen Canvas anfassen darf heisst das doch das man noch nicht mal ein TBitmap.Create im Thread machen darf ? Das Bitmap.Canvas muss dann doch irgendwie angelegt werden. Ich habe unter FMX öfters festgestellt das TBitmap.CanvasClass nicht gesetzt oder sich anders verhält (skaliert, transformiert) als z.B. ein Image.Bitmap.Canvas. Ich hätte gehofft das interne TBitmap.Canvas nur eine Simulation eines Canvas ist, und nicht weiter mit dem UI-Thread kommuniziert. Unter Fmx gibt es auch ein ![]() nur einen Pointer auf das eigentlich Bitmap setzt, wenn ich das richtig verstehe. Also könnte man versuchen so etwas in der Art zu machen: - erst alle neuen, leeren Bitmaps im UI Thread anlegen, aber mit korrektem Size - dann evtl. im Thread mit Scanline kopieren, bearbeiten (wird aber womöglich weniger effizient sein) - danach könnten die Bitmaps im UI-Thread mit der TListView BitmapRef verbunden werden - der Owner wäre danach nicht die ListView, dann muss das auch entsprechend verwaltet werden Das gefällt mir aber gar nicht, gibts nichts besseres ? Rollo |
AW: [Fmx, Vcl] Verhalten von TBitmap in Threads
Liste der Anhänge anzeigen (Anzahl: 1)
Hier ist mal ein kleiner Test.
@Sir Rufo Als Basis habe ich deinen ![]() Der TBitmapProducerThread war wohl nur für VCL gedacht, aber auch mit FMX könnte das ja Laufen ?! Ich erzeuge im Thread neue Bitmaps, die ich dann in die ImageList speichere - Thread Erzeugen eines leeren Bitmaps - UI-Thread per Syncronize Ändern der Größe (das wird mit dem Canvas in Berührung kommen) - UI-Thread Übergabe des neuen Bitmaps Im DoProduce rufe ich das ändernde SetSize mit Synchronize auf, und ändere dann mit Map/Unmap. Anders komme ich doch an die Pixel nicht heran, ich könnte es noch mit Scanline probieren aber Map/Unmap brauche ich womöglich trotzdem.
Delphi-Quellcode:
Unter Windows läuft alles gut bis jetzt,
procedure TBitmapProducerThread.DoProduce(const LBitmap : TBitmap; LParams : TBitmapParameters);
var Data: TBitmapData; I: Integer; col: TAlphaColor; begin TThread.Synchronize(nil, procedure begin // This must be in Main Thread, to be treadsafe LBitmap.Width := LParams.Width; LBitmap.Height := LParams.Height; end ); // Bitmap erstellen if LBitmap.Map(TMapAccess.ReadWrite, Data) then begin col := TAlphaColorF.Create(128+Random(127), 128+Random(127), 128+Random(127)).ToAlphacolor; // // 5000 xxx Pixel auf ein Bitmap malen for I := 1 to 5000 do begin Data.SetPixel(Random( LBitmap.Width ), //Muss noch Scanline probieren, als Alternative Random( LBitmap.Height ), col //TAlphaColorRec.Red ); end; LBitmap.Unmap(Data); end; end; unter OSX gab es schon Exception, geht aber auch meistens und unter Android geht es gar nicht. Unter iOS habe ichs noch garnicht versucht ... Aber zumindest habe ich mal getestet wie man Bitmaps einer ImageList zuordnen kann, über den Namen:
Delphi-Quellcode:
//
// wird von TBitmapProducerThread aufgerufen, im UI-Thread // procedure TForm1.EvOnBmpProdOutputChanged(Sender: TObject); var isi: TSourceItem; mbi: TBitmapItem; idi: TDestinationItem; di: TDestinationItem; lay: TLayer; sX: string; bmi: TBitmapItem; ms: TMemoryStream; bmp: TBitmap; begin bmp := TBitmap.Create; try FBmpProd.Get( bmp ); // Hier wird die Bitmap aus dem Thread geholt // Und hier wird ein Neuer ImageList-Eintrag angelegt isi := ImageList1.Source. Add as TSourceItem; if isi.MultiResBitmap.Count = 0 then begin bmi := isi.MultiResBitmap.Add as TBitmapItem; ms := TMemoryStream.Create; try bmp.SaveToStream( ms ); ms.Position := 0; bmi.Bitmap.CreateFromStream( ms ); finally ms.Free; end; end; sX := isi.Name; idi := ImageList1.Destination.Add as TDestinationItem; lay := idi.Layers.Add; lay.Name := sX; // Setup the link the the Source Bitmap here sX := lay.Name; lay.SourceRect.Rect := RectF(0, 0, 256, 256); // Region within the soure-Rect finally bmp.Free; end; ScrollBar1.Max := ImageList1.Destination.Count-1; end; Benutzung: Append/Insert/Delete/Clear - Legt Items im Listview an, und benutzt ImageList als Basis. Das geht recht fix, so das man keinen Thread braucht (ich zumindest noch nicht)). Produce erzeugt neue Bitmaps in dem ImageView, die ich dann s.o. zum Erzeugen benutzen kann. Rollo |
AW: [Fmx, Vcl] Verhalten von TBitmap in Threads
Bei FMX sollte TBitmap heute aber Threadsafe sein (Stand Delphi 10.2 Tokyo):
![]() Dabei aber unbedingt peinlich darauf achten, dass das Bitmap-Objekt nicht gleichzeitig aus mehreren Threads verwendet wird! Wenn diese Regel verletzt wird sind die Folgen schmerzhaft: Ich kämpfte gerade mit sporadisch leeren TImage denen ein Bitmap zu gewiesen wurden, welches in einem Thread erstellt wurde. |
AW: [Fmx, Vcl] Verhalten von TBitmap in Threads
Offenbar gibt's unter FMX auch unter Delphi 10.2.3 immer noch Probleme.
Siehe RSP-19673 |
Alle Zeitangaben in WEZ +1. Es ist jetzt 07:24 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