Entwickler-Ecke
Delphi Language (Object-Pascal) / CLX - Alternative zu Synchronisation in Threads
HenryHux - Di 08.03.11 18:10
Titel: Alternative zu Synchronisation in Threads
Hi, bei mir werden in mehreren Threads gleichzeitig Screenshots gemacht.
Da das in den Funktionen leider nicht ohne Synchronisation geht, ist halt alles synchronisiert.
Doch bringen da dann Threads nicht wirklich einen Geschwindigkeitsvorteil, denn es kann ja immer nur eine synchronisierte Funktion ausgeführt werden.
Jetzt wollte ich mal fragen, ob es dazu irgendwelche Alternativen gibt, damit nicht immer alle Threads warten müssen, bis der andere Thread die Synchronisation freigibt.
Lg
elundril - Di 08.03.11 18:18
Du könntest gleich mehrere Prozesse starten anstatt "nur" mehrere Threads. Jeder Prozess kann dann unabhängig arbeiten. Musst das ganze dann halt mit einer art kommunikation steuern. ;)
lg elundril
HenryHux - Di 08.03.11 18:21
elundril hat folgendes geschrieben : |
Du könntest gleich mehrere Prozesse starten anstatt "nur" mehrere Threads. Jeder Prozess kann dann unabhängig arbeiten. Musst das ganze dann halt mit einer art kommunikation steuern. ;)
|
Ist ein neuer Prozess immer mit dem erneuten Öffnen der Executable verbunden, oder kann ich neue Prozesse wie Threads während der Laufzeit erzeugen?
lg
E: Auch jaenicke hat mir in nem älteren Thread schon geraten :
Zitat: |
Lagere die Prüfung nicht nur in einen eigenen Thread, sondern in einen anderen Prozess aus. Via IPC bekommst du dann das Ergebnis mitgeteilt und gut ist es. Dann sind die ganzen Synchronisierungsprobleme vom Tisch. |
Dann denke ich, werde ich das so mal realisieren. Doch wie gehe ich da am besten dran? Wäre es möglich nur die betroffenen Funktionen "auszulagern"?
jaenicke - Di 08.03.11 21:02
HenryHux hat folgendes geschrieben : |
Dann denke ich, werde ich das so mal realisieren. Doch wie gehe ich da am besten dran? Wäre es möglich nur die betroffenen Funktionen "auszulagern"? |
Theoretisch ja, praktisch würde ich das besser entkoppeln.
Die wohl beste Variante wären DataSnap oder ähnliche Sachen, aber das wird mit deiner Delphiversion kaum gehen.
Die billigste und einfachste Variante wären schlichte Windows Messages. Du kannst dem neuen Prozess einfach das Handle mitgeben, an die es die Antwort schicken soll, und damit hat sich das schon fast.
Und dann gibt es da noch eine Unmenge anderer Möglichkeiten: Pipes, MMFs, ...
Aber ich würde dir wirklich raten einfach einmal ein paar Windows Messages hin- und herzuschicken und zu schauen wie das so läuft, das wird denke ich die einfachste Lösung sein. ;-)
Der Datenaustausch kann dann zusätzlich über Shared Memory / MMFs passieren.
HenryHux - Di 08.03.11 21:06
Ok, danke so werde ich da mal drangehen.
Also habe ich das richtig verstanden, dass ich für einen neuen Prozess eine eigene Executable erstellen muss?
Und die rufe ich immer wieder/oder einmal pro Thread auf und übergebe ihr beim Start den ThreadHandle.
Was ich mich schon öfter gefragt habe, wie man solche StartParameter realisiert...
Vielen Dank,
Lg
jaenicke - Di 08.03.11 21:36
HenryHux hat folgendes geschrieben : |
Also habe ich das richtig verstanden, dass ich für einen neuen Prozess eine eigene Executable erstellen muss? |
Du kannst auch die eigene Exe wieder aufrufen. ;-)
HenryHux hat folgendes geschrieben : |
Was ich mich schon öfter gefragt habe, wie man solche StartParameter realisiert... |
Schau dir z.B. die Parameter von ShellExecute an. Einer davon heißt Parameters und da gehören die auch rein. ;-)
Abfragen geht mit ParamCount und ParamStr. ;-)
delfiphan - Di 08.03.11 21:56
An deiner Stelle würde ich versuchen, die Funktionen Thread-Safe zu machen.
jaenicke - Di 08.03.11 21:59
Wenn das so einfach wäre auf visuelle Objekte threadsicher zuzugreifen...
Keine Ahnung ob das anders ist, wenn man nur mit Handles, also DCs, arbeitet, aber ich glaube nicht.
delfiphan - Di 08.03.11 22:09
So ungefähr in dieser Reihenfolge: GetDC(GetDesktopWindow), CreateCompatibleDC, CreateCompatibleBitmap, SelectObject, BitBlt
Wenn du Handles nicht über Threadgrenzen hinweg verwendest, solltest du keine Schwierigkeiten bekommen.
Alternativ kann man auch über DirectX oder Windows Media Screenshots erstellen.
Ich frage mich allerdings, was du denn damit anstellen willst. Wieso in mehreren Threads gleichzeitig Screenshots? Wenn man die gleichzeitig macht, kriegt man auch das gleiche Ergebnis.
HenryHux - Di 08.03.11 22:37
Die Positionen von den Screenshots sind verschieden und werden auch anders verarbeitet und das bekomme ich nicht in einen Thread.
Ich habe im Prinzip nur 2 Funktionen die synchronisiert werden. Einmal aus einer ImgList die Methode GetBitmap, die stört mich aber nicht groß, weil die nicht viel Zeit in Anspruch nimmt.
Außerdem könnte ich da leicht auf alternative Methoden umspringen. Anders siehts da bei der Prozedur für den Screenshot aus. Wäre natürlich ideal, wenn ich die erst garnicht synchronisieren bräuchte.
Wie könnte es denn klappen mit den Methoden die du angesprochen hast?
Delphi-Quelltext
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:
| procedure TMain.Screenshot; var DesktopHandle: HDC; ShotTargetBitmap : TBitmap; begin if not Assigned(SynchP.TargetBmp) then Exit; Shottargetbitmap := TBitmap.Create; try ShotTargetbitmap.Width := SynchP.Bounds.Right - SynchP.Bounds.Left; ShotTargetbitmap.Height := SynchP.Bounds.Bottom - SynchP.Bounds.Top; SynchP.TargetBmp.Width := SynchP.Rechts - SynchP.Links; SynchP.TargetBmp.Height := SynchP.Unten - SynchP.Oben; DesktopHandle := GetDC(0); try BitBlt(ShotTargetBitmap.Canvas.Handle, 0 ,0, SynchP.bounds.Right-SynchP.bounds.left, SynchP.bounds.Bottom - SynchP.bounds.top, DesktopHandle, SynchP.bounds.Left, SynchP.bounds.Top,SRCCOPY); BitBlt(SynchP.TargetBmp.Canvas.Handle, 0, 0, SynchP.Rechts, SynchP.Unten, ShotTargetBitmap.Canvas.Handle, SynchP.Links, SynchP.Oben, SRCCOPY); finally ReleaseDC(0, DesktopHandle); end; finally FreeAndNil(ShotTargetBitmap); end; end;
procedure TMain.ScreenshotSynched(Bounds2:TRect; Links2, Rechts2, Oben2, Unten2 : Integer; var Bmp : TBitmap); begin with SynchP do begin Bounds := Bounds2; Links := Links2; Rechts := Rechts2; Oben := Oben2; Unten := Unten2; end; SYNCHRONIZE(ScreenShot); Bmp.Assign(SynchP.TargetBmp); end; |
Screenshots werden nur im jeweiligen Thread benötigt, der auch die Funktion aufruft.
Ich bin für jeden Vorschlag offen =)
Lg
HenryHux - Mi 09.03.11 17:00
Wollte nochmal kurz nachfragen.
Das einzige was in der Prozedur oben synchronisiert werden muss ist doch das GetDC(0);, BitBlt muss soweit ich weiß doch nicht synchronisiert werden, oder? (ausgenommen das Erstellen der Bitmaps, die könnte man ja auch anders erzeugen)
Zumindest, wenn ich mit der Annahme richtig liege dürfte es doch genügen aus dem DesktopHandle := GetDC(0); ein CreateCompatibleDC(GetDesktopWindow); zu machen?
Lg
jaenicke - Mi 09.03.11 17:31
Schmeiß einfach TBitmap komplett heraus. Nach dem was
delfiphan geschrieben hat, sollte das klappen, wenn du nur mit den Handles arbeitest.
HenryHux hat folgendes geschrieben : |
Das einzige was in der Prozedur oben synchronisiert werden muss ist doch das GetDC(0);, BitBlt muss soweit ich weiß doch nicht synchronisiert werden, oder? |
Genau das wird vermutlich knallen, denn dann erzeugst du das Handle in einem anderen Kontext als dein Thread. Aber ich habe mich damit nie so genau beschäftigt, nur eben gemerkt, dass TBitmap im Thread keine gute Idee ist.
delfiphan - Mi 09.03.11 20:10
Fehlerbehandlung darfst du noch selbst implementieren. Du bekommst hier einfach ein HBITMAP. Ob du damit arbeiten kannst, ist eine andere Frage. Mit GetDIBits kommst du z.B. an die Rohdaten.
Delphi-Quelltext
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:
| type TScreenShot = class private FBitmap: HBITMAP; procedure Clear; public property Bitmap: HBITMAP read FBitmap; procedure Execute(Rect: TRect); destructor Destroy; override; end;
procedure TScreenShot.Clear; begin DeleteObject(FBitmap); FBitmap := 0; end;
destructor TScreenShot.Destroy; begin Clear; inherited; end;
procedure TScreenShot.Execute(Rect: TRect); const CAPTUREBLT = $40000000; var Width, Height: Integer; DesktopWindow: HWND; SourceDC, TargetDC: HDC; begin if FBitmap <> 0 then Clear;
Width := Rect.Right - Rect.Left; Height := Rect.Bottom - Rect.Top;
DesktopWindow := GetDesktopWindow; SourceDC := GetDC(DesktopWindow); TargetDC := CreateCompatibleDC(SourceDC); FBitmap := CreateCompatibleBitmap(SourceDC, Width, Height); SelectObject(TargetDC, FBitmap); BitBlt(TargetDC, 0, 0, Width, Height, SourceDC, Rect.Left, Rect.Top, SRCCOPY or CAPTUREBLT); ReleaseDC(DesktopWindow, SourceDC); DeleteDC(TargetDC); end; |
HenryHux - Mi 09.03.11 20:37
Vielen Dank, erste Versuche sahen schonmal vielversprechend aus.
Mit einem HBITMAP kann ich direkt nichts anfangen, aber
http://www.delphipraxis.net/49185-hbitmap-tbitmap.html hat geholfen =)
Wenn alles geklappt hat, oder auch nicht, melde ich mich nochmal.
Lg
HenryHux - Mi 09.03.11 22:52
So..
Erstmal nochmal vielen Dank, es klappt im Ansatz.
Doch habe ich noch ein Problem.
Wenn ich mehrere Threads jetzt hiermit Screenshots machen lasse :
Delphi-Quelltext
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:
| procedure TScreenShot.Execute(Bounds: TRect); const CAPTUREBLT = $40000000; var Width, Height: Integer; DesktopWindow: HWND; SourceDC, TargetDC: HDC; begin if FBitmap <> 0 then Clear;
Width := Bounds.Right - Bounds.Left; Height := Bounds.Bottom - Bounds.Top;
DesktopWindow := GetDesktopWindow; SourceDC := GetDC(DesktopWindow); TargetDC := CreateCompatibleDC(SourceDC); FBitmap := CreateCompatibleBitmap(SourceDC, Width, Height); SelectObject(TargetDC, FBitmap); BitBlt(TargetDC, 0, 0, Width, Height, SourceDC, Bounds.Left, Bounds.Top, SRCCOPY or CAPTUREBLT);
ReleaseDC(DesktopWindow, SourceDC); DeleteDC(TargetDC); end;
procedure TScreenshot.Cut(Links, Rechts, Oben, Unten : Integer; SourceBitmap : TBitmap; var TargetBmp : TBitmap); begin TargetBmp.Width := Rechts - Links; TargetBmp.Height := Unten - Oben; Windows.BitBlt(TargetBmp.Canvas.Handle, 0, 0, rechts, unten, SourceBitmap.Canvas.Handle, links, oben, SRCCOPY); randomize; TargetBmp.SaveToFile('L:\Tests\'+inttostr(random(99999))+'.bmp'); end; |
und so der Aufruf :
Delphi-Quelltext
1: 2: 3: 4:
| TS.Execute(Rect(Links,Oben,Links+100,Oben+100)); SourceBmp.Handle := TS.Bitmap;
TS.Cut(50,100,90,100, SourceBmp, TargetBmp); |
(Bitmaps sind nicht im Kontext des Threads erzeugt).
, dann bleiben von 100 Screenshots vlt 5-10 einfach weiß. Auch wenns die ganze Zeit die gleiche Stelle ist, springt eins einfach aus der Reihe...
Ist aber auch, wenn ich nur einen Thread laufen lasse, verstärkt sich nicht bei mehreren Threads.
Kommt jemand spontan auf ne Idee?
Lg
Edit: Ich habe jetzt mal nicht die kleinen geschnittenen Bitmaps abgespeichert, sondern die nicht geschnittenen.
Und dort ist zu erkennen, dass nicht einfach nichts auf dem Bild ist, sondern das Interface von Delphi, was jedoch während der Zeit minimiert war :idea: :?:
Ich vermute mal stark, dass es hieran liegt :
DesktopWindow := GetDesktopWindow;.
Wie ist denn die Bezeichnung für den ganzen Screen?
delfiphan - Mi 09.03.11 23:13
Bitmaps sind Kernelobjekte und der Kernel hat bekanntlich nur max. 2 GB auf einem 32-bit System (unabhängig von den Prozessen). Da nützt es auch nichts, 4 GB RAM zu haben. Ausserdem kann Speicherfragmentierung dazu führen, dass der Kernel keinen grossen, zusammenhängenden Block mehr finden kann; auch wenn insgesamt noch genügend Speicher bzw. Virtueller Address Space da wäre. Gibst du die Objekte auch immer schön frei? Hast du eine Fehlerbehandlung reinprogrammiert? Allenfalls gibt eine GDI-Funktion 0 zurück, dann solltest du mal schauen, was die Fehlermeldung war.
Ausserdem verwendest du ja immer noch TBitmap um das ganze rauszulesen. Ich bin mir nicht sicher, ob TBitmap thread-safe ist oder es sicher ist, einem TBitmap ein Handle aus einem anderen Thread zuzueweisen. Da müsste man sich mal den Code anschauen. Ausserdem bin ich mir nicht sicher, ob du Handles jetzt doch threadübergreifend verwendest (evtl. indirekt über TBitmap). Da muss man etwas aufpassen.
elundril - Mi 09.03.11 23:23
delfiphan hat folgendes geschrieben : |
Bitmaps sind Kernelobjekte und der Kernel hat bekanntlich nur max. 2 GB auf einem 32-bit System (unabhängig von den Prozessen). |
Ich dachte 3,4 GB oder so wären möglich beim 32-bit system.
jaenicke - Do 10.03.11 06:41
Du verwechselst gerade den Speicher, den Windows zur Verfügung hat mit dem virtuellen Adressraum, den jeder Prozess und auch der Kernel zur Verfügung haben, denn das sind 2 GiB. ;-)
Gerd Kayser - Do 10.03.11 08:18
jaenicke hat folgendes geschrieben : |
Adressraum, den jeder Prozess und auch der Kernel zur Verfügung haben, denn das sind 2 GiB. ;-) |
Wenn ich u. a. die XE-Hilfe zu den Compiler-Direktiven richtig interpretiere, kann man das aber ändern. Zitat: "IMAGE_FILE_LARGE_ADDRESS_AWARE Die Anwendung kann Adressen verarbeiten, die größer als 2 GB sind."
jaenicke - Do 10.03.11 09:18
Auch dann gehen aber nur 3 GiB und das auch nur, wenn Windows entsprechend konfiguriert ist, zumindest unter 32-Bit Systemen.
Gerd Kayser - Do 10.03.11 10:21
jaenicke hat folgendes geschrieben : |
Auch dann gehen aber nur 3 GiB und das auch nur, wenn Windows entsprechend konfiguriert ist, zumindest unter 32-Bit Systemen. |
Bei einer 32-Bit-Anwendung unter Windows7 (32 Bit) bis zu 3 GB, bei 64-Bit-Windows 4 GB.
http://msdn.microsoft.com/en-us/library/aa366778(VS.85).aspx#physical_memory_limits_windows_7
Windows 7 zu konfigurieren ist auch kein Hexenwerk.
Setzen des Wertes bei Windows: BCDEDIT /Set IncreaseUserVa nnnn (nnnn im MB)
Löschen des Wertes: BCDEDIT /deletevalue IncreaseUserVa
HenryHux - Do 10.03.11 15:21
Soa, hab jetzt den Fehler entdeckt.
Da kann man nur noch :autsch: :autsch: :autsch: :autsch: :autsch: :autsch: :autsch: :autsch:
Die Anweisung
TargetBmp.SaveToFile('L:\Tests\'+inttostr(random(99999))+'.bmp'); war schuld.
Ich dachte, dass die Bilder chronologisch im Ordner angezeigt würden, da lag ich aber falsch, die wurden nach Namen abgespeichert. Und da ich am Ende immer Delphi aufgerufen habe um das Progg zu beenden, haben sich da nen paar reineschlichen und sich vermischt. Jetzt klappt alles perfekt, auch mit mehreren Threads.
Danke, hat mir ne Menge Arbeit gespart =)
Habe aber noch ein kleines Problem, ist von ähnlicher Natur.
Und zwar greife ich synchronisiert mit der Methode GetBitmap auf eine ImgList zu, was aber auch einige Zeit in Anspruch nimmt.
Sieht schlicht so aus :
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
| procedure TMain.GetImgLstSynched(pos : integer; var bmp : TBitmap); begin ColorL.Nr := Pos; Synchronize(GetImgLst); bmp.Assign(ColorL.Bmp); end;
procedure TMain.GetImgLst; begin GUI.ImgLst.GetBitmap(ColorL.Nr, ColorL.Bmp); end; |
Muss ich hier auf Alternativen zugreifen, wie z.b auf der HD gespeicherte Bilder, oder auf Ressourcen, oder gibt es auch hier eine Möglichkeit das ohne Synchronisation auszuführen?
Lg
HenryHux - Fr 11.03.11 17:50
So, habe es jetzt mit Resource-Files gemacht, weiß nicht wieso nicht direkt so.
Danke!
Lg
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2025 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!