Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   GUI-Design mit VCL / FireMonkey / Common Controls (https://www.delphipraxis.net/18-gui-design-mit-vcl-firemonkey-common-controls/)
-   -   TBitmap ist NICHT threadsave (https://www.delphipraxis.net/189446-tbitmap-ist-nicht-threadsave.html)

Kraisel 13. Jun 2016 21:13

TBitmap ist NICHT threadsave
 
Hallo Zusammen,

dies ist keine Frage, sondern nur eine getestete Aussage. Denn immer mal wieder tritt die Frage auf, ob TBitmap threadsave ist oder nicht. Wie viele andere Kollegen auch, war ich der Meinung, dass man durchaus mit Map/Unmap in einem Thread wenigstens aus der Bmp lesen kann. Aber selbst das geht manchmal schief und es werden falsche Pixel gelesen. Ob hier ein falscher Buffer zurückgegeben wird oder ob die Inhalte nicht stimmen, habe ich nicht weiter untersucht.

Hier die getestete Antwort:

TBitmap ist NICHT threadsave. (getestet mit XE10.1 Berlin und FMX.)

Das Böse :oops: daran ist, dass man vieles nicht merkt, aber die Bilder sehen u.U. im Thread anders aus als gezeichnet.

Testaufbau:

1) Es werden im Mainthread eine zufällige BmpSize, 100 zufällige Koordinaten, 100 zufällige Farben, 100 zufällige Opacities usw. in einen Buffer geschrieben.
2) Eine Methode BmpBuilder baut eine Bmp mit dem Inhalt dieses Buffers, z.B. 100 Linien mit 100 verschiedenen Farben, die ja vorher per Zufall bestimmt wurden.
3) Im MainThread wird eine Bmp Referenz mit dem BmpBuilder generiert.
4) In 6 TTasks wird je eine eigene Bmp mit demselben BmpBuilder generiert.
5) Dann werden die Bmps im Mainthread oder auch in der Task mit der Referenz Pixelweise verglichen.
6) Dann beginnt alles wieder mit Schritt 1 mit neuen zufälligen Werten.

Ergebnisse:

Mit Synchronize im BmpBuilder: eine Fehler erkennbar, auch nach Stunden und Millionen Bmp Vergleichen festgestellt.

Ohne Synchronize:, also TBitmap-Methoden direkt im Thread bemutzt:
Schneller Absturz bei Benutzung von z.B. TextWidth, TextHeight und FillText. OK, ... das bekommt man ja mit.

Aber folgendes bekommt man nicht mit:

z.B. die Canvas-Methoden Begin/EndScene, Map/Unmap, DrawLine und einige mehr, stürzen nicht ab, aber die generierte Bmp
zeigt zufällige Fehler im Bild mit einer Wahrscheinlichkeit von 1..10% der getesteten Bmps (mehrfach mehrere Millionen Bmps getestet)?

Nicht weiter untersucht, da ausreichend fuer die Beurteilung, dass alle TBitmap-Methoden nur im Mainthread benutzt werden sollten.

Bernhard Geyer 13. Jun 2016 21:57

AW: TBitmap ist NICHT threadsave
 
TBitmap verwendet Windows-Ressourcen.
Und diese sind per Defintion Thread-Affine und sind nur im erzeugenden Thread gültig

Kraisel 14. Jun 2016 01:08

AW: TBitmap ist NICHT threadsave
 
Unter FMX (Firemonkey) kann man nicht unbedingt von Windows-Resourcen ausgehen.

Bernhard Geyer 14. Jun 2016 07:42

AW: TBitmap ist NICHT threadsave
 
Zitat:

Zitat von Kraisel (Beitrag 1340076)
Unter FMX (Firemonkey) kann man nicht unbedingt von Windows-Resourcen ausgehen.

Wenns unter Windows läuft vermutlich schon (auch wenn ich die Details der FMX-Implementierung nicht kenne)

Sir Rufo 14. Jun 2016 08:10

AW: TBitmap ist NICHT threadsave
 
Die Aussage von Marco Cantu dazu lautet "
Delphi-Quellcode:
TBitmap
ist niemals für die Verwendung ausserhalb des MainThreads gedacht gewesen." (egal ob VCL/FMX).

Von daher ist diese Erkenntnis nicht wirklich neu oder überraschend.

Harry Stahl 14. Jun 2016 08:18

AW: TBitmap ist NICHT threadsave
 
Hallo Peter,

kannst Du Deine Testroutine hier mal hochladen?

Würde es gerne selber mal nachvollziehen.

himitsu 14. Jun 2016 12:15

AW: TBitmap ist NICHT threadsave
 
TBitMap und vorallem dessen TCanvas verwenden globale Instanzen von verschiedenen Delphiressourcen,
wie z.B. Fonts, Brushs, Pens usw.

Das kann also nicht so einfach Threadsave werden, so lange noch irgendwo auf etwas Globales zugriffen wird.



Abgesehn davon, daß in Windows GDI-Ressourcen an den erstellenden Thread gebunden sind.

Der schöne Günther 14. Jun 2016 12:32

AW: TBitmap ist NICHT threadsave
 
Zitat:

Zitat von Sir Rufo (Beitrag 1340090)
Die Aussage von Marco Cantu dazu lautet "
Delphi-Quellcode:
TBitmap
ist niemals für die Verwendung ausserhalb des MainThreads gedacht gewesen." (egal ob VCL/FMX).

Von daher ist diese Erkenntnis nicht wirklich neu oder überraschend.

Vorausgesetzt man geht davon aus dass die offizielle Doku nicht reicht und man zusätzlich immer Community-Foren, Twitter, Google und sonstwas-Accounts von bestimmten Leuten folgen muss um die Bibliotheken fehlerfrei verwenden zu können.

Sir Rufo 14. Jun 2016 14:40

AW: TBitmap ist NICHT threadsave
 
Zitat:

Zitat von Der schöne Günther (Beitrag 1340132)
Zitat:

Zitat von Sir Rufo (Beitrag 1340090)
Die Aussage von Marco Cantu dazu lautet "
Delphi-Quellcode:
TBitmap
ist niemals für die Verwendung ausserhalb des MainThreads gedacht gewesen." (egal ob VCL/FMX).

Von daher ist diese Erkenntnis nicht wirklich neu oder überraschend.

Vorausgesetzt man geht davon aus dass die offizielle Doku nicht reicht und man zusätzlich immer Community-Foren, Twitter, Google und sonstwas-Accounts von bestimmten Leuten folgen muss um die Bibliotheken fehlerfrei verwenden zu können.

Wieso das denn jetzt?

Mir war eigentlich schon immer bewusst, dass alles was mit VCL zu tun hat nicht threadsafe ist. Analog gilt das auch für FMX.

Wenn man nicht weiß, wozu die Klasse XY nun gehört, dann schaut man sich den Namespace an und weiß sofort, wo man kein threadsafe erwarten kann
Delphi-Quellcode:
Vcl.Graphics.TBitmap // <- Vcl davor => NEIN
FMX.Graphics.TBitmap // <- FMX davor => NEIN
Prinzipiell ist eigentlich alles erst einmal als nicht threadsafe anzusehen. Die Dokumentation sollte es explizit hervorheben was threadsafe ist und nicht umgekehrt, sonst sieht man vor lauter nicht threadsafe den Thread nicht mehr.

VCL und FMX sind zudem immer noch als MainThread-Affin anzusehen (darum geht es hier eigentlich und nicht um threadsafe)

Delphi-Laie 14. Jun 2016 15:37

AW: TBitmap ist NICHT threadsave
 
Zitat:

Zitat von Sir Rufo (Beitrag 1340090)
Die Aussage von Marco Cantu dazu lautet "
Delphi-Quellcode:
TBitmap
ist niemals für die Verwendung ausserhalb des MainThreads gedacht gewesen." (egal ob VCL/FMX).

In meinem Sortierkino läuft TBitmap in Extra-, also Nicht-VCL- bzw. Nichtmainthreads wie gewünscht.

Bernhard Geyer 14. Jun 2016 16:55

AW: TBitmap ist NICHT threadsave
 
Zitat:

Zitat von Delphi-Laie (Beitrag 1340163)
Zitat:

Zitat von Sir Rufo (Beitrag 1340090)
Die Aussage von Marco Cantu dazu lautet "
Delphi-Quellcode:
TBitmap
ist niemals für die Verwendung ausserhalb des MainThreads gedacht gewesen." (egal ob VCL/FMX).

In meinem Sortierkino läuft TBitmap in Extra-, also Nicht-VCL- bzw. Nichtmainthreads wie gewünscht.

Das Problem ist die wechsel des Threads und hier das Hauptproblem auf MS/Windows-Seite.
Alle GUI-Ressourcen von Windows sind nur im erzeugenden Thread gültig.
D.h. wenn du dein Bitmap nur in einem Thread erzeugst, bearbeitest und freigibst ist alles gut.
Damit hat auch die VCL kein Problem da hier keine weiteren Abhängigkeiten zu Formular, Screen und Co. existiert.

bernau 14. Jun 2016 17:26

AW: TBitmap ist NICHT threadsave
 
Zitat:

Zitat von Bernhard Geyer (Beitrag 1340171)
Alle GUI-Ressourcen von Windows sind nur im erzeugenden Thread gültig.
D.h. wenn du dein Bitmap nur in einem Thread erzeugst, bearbeitest und freigibst ist alles gut.

Und das ist es anscheinend nicht.

himitsu 14. Jun 2016 17:37

AW: TBitmap ist NICHT threadsave
 
Kämpf dich doch mal durch den Code von TBitmap,
also vorallem wo das Canvas her kommt und speziell die darin vorhandenen TPen oder TBrush.

Da landet man z.B. beim TResourceManager, der die Resourcen threadabhängig verwaltet.

Kraisel 14. Jun 2016 18:33

AW: TBitmap ist NICHT threadsave
 
Hallo Zusammen,

OK, ... scheint also doch immer noch ein kleines Problem mit sehr unterschiedlichen Erfahrungen zu sein.

Wie ich schon sagte, ist mir seit den Anfängen von Delphi klar, dass vieles in der VCL bzw. in FMX NICHT threadsave ist. Und es ist auch richtig, dass man grundsätzlich unterstellen sollte, dass alles, was man benutzt, vielleicht NICHT threadsave ist.

Ich hatte aber gehofft, dass ich in einem Thread eine FMX-Bitmap mit Map öffnen kann und dann selber Pixel lesen und/oder manipulieren kann. Aber selbst das geht eben in 1..10 % der Fälle schief und man merkt es nicht, da evtl. nur einige Pixel falsch sind. Das hatte ich einfach nicht erwartet.

Wohlgemerkt, ein Synchronize über den Bmp-Methoden im Thread und alles ist wieder gut. Deshalb gehe ich auch davon aus, dass mein Test OK ist, was man natürlich auch noch hinterfragen kann.

Wie Harry vorgeschlagen hat, werde ich deshalb, wenn ich Zeit habe, meinen Test aus meinem Framework separieren und hier hochladen. Das kann aber etwas dauern, da ich wie 'immer' unter Zeitmangel leide.

Sir Rufo 14. Jun 2016 19:10

AW: TBitmap ist NICHT threadsave
 
Es gibt einen Unterschied zwischen threadsafe und threadaffin. Die meisten Klassen sind nicht threadsafe aber - gottlob - auch nicht threadaffin.
  • threadsafe
    Von jedem Thread kann auf die Eigenschaften der Klasse zugegriffen werden.
  • threadaffin
    Auf die Eigenschaften der Klasse darf nur im Erzeuger-Thread zugegriffen werden
  • ganz blöd
    Die Klasse ist noch mit einem globalen (oder wie auch immer aufgehängtem) Kontext verwoben, der auch noch threadaffin ist (idR. mit dem MainThread)

Delphi-Quellcode:
TBitmap
gehört eindeutig zur Kategorie ganz blöd.

Wenn jemand so etwas in einem Thread verwendet und keine Probleme damit hat, dann jeden Abend das in das Nachtgebet mit einschliessen und weiter hoffen. Es ist keine zugesicherte Eigenschaft und läuft wohl eher durch Zufall (oder man hat sich irgendwo durch die Hintertür eine Synchronisation eingefangen). Anyway, empfehlen würde ich es nicht und darauf bauen erst recht nicht.

Aber eine Hummel kann eigentlich auch nicht fliegen und tut es trotzdem, weil ihr die Physik und Aerodynamik fremd und sowas von egal sind. :stupid:

Rollo62 14. Jun 2016 19:11

AW: TBitmap ist NICHT threadsave
 
Zitat:

D.h. wenn du dein Bitmap nur in einem Thread erzeugst, bearbeitest und freigibst ist alles gut.
Zitat:

wo das Canvas her kommt
Könnte es sein das Beides stimmt ?

Wenn man nur das BMP im Speicher anlegt und über Scanline auf die Pixel zugreift ist es im Thread machbar,
sobald ein Canvas oder was anderes in Spiel kommt ist es MainThread affin ?

Ich halte es so das ich nur im Mainthread drauf zugreife, weil mir diese Aussagen auch nicht 100% sicher sind.

Rollo

Sir Rufo 14. Jun 2016 19:14

AW: TBitmap ist NICHT threadsave
 
@Rollo62

Es gibt keine konkrete Aussage darüber, was man von
Delphi-Quellcode:
TBitmap
gefahrlos im Thread verwenden kann und was nicht - es hat beim Klassendesign keinen interessiert.

Zugesichert wird nur das korrekte Verhalten im MainThread.

Den Rest muss man sich durch SourceCode-Wälzen zusammensuchen. Einfaches Ausprobieren reicht da leider nicht aus, denn eine race condition macht sich meist erst dann bemerkbar, wenn man gerade nicht hinschaut (Murphys Law)

Kraisel 15. Jun 2016 00:17

AW: TBitmap ist NICHT threadsave
 
Kategorie "ganz blöd" kannte ich noch nicht, aber sofort in mein Repertoire aufgenommen. :-D

Wenn es unbedingt parallel sein muss, geht es so:

1) MainThread: Kopie einer Bmp in ein allociertes Memory, das geht sau schnell, wenn man das Memory genau wie die Bmp organisiert.
2) SomeThread: Manipulieren des Memories, vielleicht den alten Brasenham-Algorithmus wieder aus dem Keller holen. Aber bitte an den Rändern richtig clippen. Sonst gibt es Überraschungen.
3) MainThread: Kopie des Memories zurück in die Bmp, das geht wieder schnell.

So mache ich z.B. 3D - Rotationen (ohne die 3D-Engine vom System) mit mehreren Threads gleichzeitig. Das funktioniert einwandfrei. Man muss nur schmerzfrei bleiben, bis alles läuft.

Für viele Aufgaben, die man hat, kann man ja eine eigene TcMemBmp-Class bauen. Bilder abdunkeln, aufhellen, oder sogar Linien zeichnen ist ja trivial. Benötigt man aber den gesamten Comfort des Canvas oder will rendern, sollte man wohl besser im MainThread bleiben.

Wahrscheinlich könnte man auch eine dll schreiben, die nichts anderes macht, als Bitmaps zeichnen. Aber das ist irgendwie auch "ganz blöd".

Kraisel 15. Jun 2016 00:43

AW: TBitmap ist NICHT threadsave
 
Vielleicht kann man aber folgendes machen:

1) MainThread: Bmp.map aufrufen.
2) SomeThread: Direktes Manipulieren der TBitmapData.Data.
3) MainThread: Bmp.unmap aufrufen.

Das müsste ja OK sein, solange der MainThread die Bmp nicht anrührt, bis die Threads fertig sind. Oder?

BUG 15. Jun 2016 01:34

AW: TBitmap ist NICHT threadsave
 
Zitat:

Zitat von Kraisel (Beitrag 1340206)
Oder?

Disclaimer: Ich hab keinen Einblick wie das genau implementiert ist.

Wenn der Mainthread mit einem blanken Zeiger auf den Daten herumholzen kann, sollte das der Hilfsthread auch können. Denke aber daran, dass der Ablauf dann richtig synchronisiert werden muss.

Kraisel 16. Jun 2016 21:18

AW: TBitmap ist NICHT threadsave
 
Abschlusserkenntnisse:

Folgende Aussagen kann ich für XE10.1 und Firemonkey bestätigen:

So, ... und wohl nur so funktioniert es mit TBitmap im Thread einwandfrei. Passt auch zu einigen vorangegangenen Kommentaren.

1) MainTread: TBitmap.Create, dann beliebig auf dem Canvas herummalen
2) MainTread: Bitmap.Map (ReadWrite)
3) SomeThreads: Beliebig "zu Fuß" in den Buffer malen und/oder lesen. (Je Zugriff natürlich mit anderen Threads, falls vorhanden, verriegeln).
4) MainTread: Warten, bis Threads fertig sind, Bitmap.Unmap, und beliebig weiterbenutzen

Ich habe noch einmal ca. 4.5 Millionen TBitmaps (jeweils Zufallgroesse 1:1 .. 1280:800) erzeugt und im threadgetriebenen Stresstest (4 TTasks) 1.2 Tera Pixel verglichen (ca. 8 Stunden).

Das einzige, was man laut meinem Stresstest im Thread machen darf, ist auf die gemapten BitmapData lesend und schreibend zugreifen.

Alles andere macht Probleme und zwar sofort bis "fast nie". Letzteres ist TBitmap.Create/Free und/oder TBitmap.Map/Unmap im Thread. Das geht nur ca. jedes 1000 mal schief und es stimmen einzelne Pixel nicht mehr. Ein später kaum zu findener Fehler.

Harry Stahl 16. Jun 2016 22:25

AW: TBitmap ist NICHT threadsave
 
Mr. Spock würde sagen "Faszinierend". Also, mit welcher Konsequenz und Umfang Du Deine Tests hier durchziehst:thumb:.

Nur ist das so ohne ein wenig Code zu sehen leider wenig transparent und daher nicht so richtig nachvollziehbar. So ein paar Codefragmente wären schon interessant, z.B. wie Du die Maping und Unmapping Sequenzen konkret umsetzt und die Threads aufsetzt.

Du brauchst ja hier auch gar nicht Deinen ganzen Test hochladen, der vollständig kompiliert, die grundlegenden Abschnitte wo Mainthread und Einzelthreads in Konkurrenz zueinander arbeiten, das würde ja schon reichen.

Und insofern im Vergleich zu sehen, was nicht geht und was geht.

Rollo62 18. Jun 2016 11:56

AW: TBitmap ist NICHT threadsave
 
Hallo Kraisel,

sehr interesant. Es müsste mit dem map/unmap ja besser gekapselt sein als unter VCL.

Trotzdem muss ich mich Harry anschliessen, wäre gut zu wissen was genau. Z.B. auch: Läuft das unter ios Andoid osx win64 ?


Die Probleme komen ja meist wenn man sich von Win32 wegbewegt
(Hätte nie gedacht das ich sowas mal sagen würde).

Rollo


Alle Zeitangaben in WEZ +1. Es ist jetzt 11:01 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