Autor Beitrag
Stephan.Woebbeking
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 97



BeitragVerfasst: Mi 25.05.11 18:05 
Hallo *!

Ich kann es zwar selbst kaum glauben, aber ich bekomme - so einfach es scheint - das hier nicht hin:

Ich will ein Double Buffering realisieren, weil die Zeichenroutine einiges an Berechnungen zu erledigen hat. So eilig hab ich's nicht, also hab ich mir einen TThread gebaut und lass über den (aus dem Formular angestossen) alle drei Sekunden eine globale TBitmap befüllen. Über das Repaint Event kopier ich die Bitmap dann in den Canvas meiner TPaintBox. Klappt alles super. Aber leider nur knapp 10 Sekunden, dann scheint die Draw() Methode nix mehr zu tun. Der Debugger zeigt, dass sowohl die Zeichenroutine als auch die Draw angefahren wird.

Mittlerweile habe ich fast alles rausgestrichen, also hoffe ich ist's nicht mehr zu komplex?

Warum funktionieren die Style/Bereichs... Tags hier im Forum nicht mehr???

Ok, die Zeichenfunktion - mittlerweile stark vereinfacht:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
procedure TPaintDisplayThread.PbxDisplayPaint();
begin
  if ( ( bitmap.Width <> display.Width ) OR ( bitmap.Height <> display.Height ) ) then begin
    bitmap.Width  := display.Width;
    bitmap.Height := display.Height;
    bitmap.Canvas.Brush.Color := clWhite;
    bitmap.Canvas.Brush.Style := bsSolid;
    bitmap.Canvas.Pen.Color := clBlack;
    bitmap.Canvas.Rectangle( 00, bitmap.Width, bitmap.Height );
  end;
  bitmap.Canvas.Pen.Color := clGreen;
  bitmap.Canvas.MoveTo( 2000 );
  bitmap.Canvas.LineTo( 600200 );
  bitmap.Canvas.Pen.Color := clRed;
  bitmap.Canvas.MoveTo( 1000 );
  bitmap.Canvas.LineTo( 500200 );
end


Und so sieht meine Repaint für die PaintBox aus:
Wie gesagt, die weiße Linie ist immer da - logisch. Die Bitmap verschwindet nach einiger Zeit - versteh ich nicht.

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
procedure TFormContinuousSpectrum.PbxSpectrumPaint(Sender: TObject);
begin
  PbxDisplay.Canvas.Pen.Color := clWhite;
  PbxDisplay.Canvas.MoveTo( 00 );
  PbxDisplay.Canvas.LineTo( 500100 );
  PbxDisplay.Canvas.Pixels[ 1010 ] := clWhite;
  PbxDisplay.Canvas.CopyMode := cmSrcCopy;
  PbxDisplay.Canvas.Draw( 00, bitmap );
end;


Das Zeichnen wird alle 3s ausgelöst:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
procedure TFormContinuousSpectrum.TmrDisplayTimer(Sender: TObject);
begin
  ...
    displayThread.PaintDisplay;
    PbxDisplay.Repaint;
  ...
end;


Sieht irgendjemand ein Problem???

Danke,
Stephan

26.05.2011, edit: Methodenheader eingefügt, damit der Programmfluss besser ersichtlich wird.

Moderiert von user profile iconNarses: Code- durch Delphi-Tags ersetzt


Zuletzt bearbeitet von Stephan.Woebbeking am Do 26.05.11 09:37, insgesamt 1-mal bearbeitet
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19339
Erhaltene Danke: 1752

W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Mi 25.05.11 18:11 
Ich hoffe du vergisst nicht jegliche Zugriffe auf die Bitmap durch den Thread mit dem Hauptthread zu synchronisieren. Sonst kann das nicht stabil laufen.

Denn du darfst niemals auf visuelle Elemente (Formular, Bitmap, ...) aus einem Thread heraus zugreifen, wenn du nicht ganz genau weißt was du tust...

user profile iconStephan.Woebbeking hat folgendes geschrieben Zum zitierten Posting springen:
Warum funktionieren die Style/Bereichs... Tags hier im Forum nicht mehr???
Es gab ein Update für das Forum, leere einfach deinen Cache. ;-)
HenryHux
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 542
Erhaltene Danke: 33

Windows 7 Premium
Delphi XE, Eclipse
BeitragVerfasst: Mi 25.05.11 18:13 
Hey,

ich weiß nicht, wo hier genau das Problem ist, jedoch sind TBitmaps nicht Threadsafe.
Versuche es mal mit einem Timer oder mit Klicks um zu testen ob es dann klappt.
Du könntest es auch synchronisieren.

lg

Edit: Da war wohl jemand schneller :D
Martok
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 3661
Erhaltene Danke: 604

Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
BeitragVerfasst: Mi 25.05.11 18:21 
Das hat irgendwas mit der Thread-Synchronisierung zu tun.
Auch mit ordentlichem Locking wirst du nicht viel ändern können... jedenfalls ging mir das so als ich das Problem mal hatte. Leider ohne sinnvolle Lösung, wie gesagt.

_________________
"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."
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19339
Erhaltene Danke: 1752

W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Mi 25.05.11 18:34 
Das geht mit Synchronisierung schon, aber die Bitmap muss auch im Kontext des Hauptthreads erzeugt worden sein.

Sinnvoller ist aber meistens, einfach die Ergebnisse per Event dem Hauptthread zu geben und den zeicnen zu lassen.
Stephan.Woebbeking Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 97



BeitragVerfasst: Do 26.05.11 09:47 
Danke schon mal für die Beiträge! Deutet ja alles auf die Synchronisation hin; da hab ich in anderen Sprache auch schon entsprechende zeitraubende Erfahrungen gesammelt und deshalb auch schon drüber nachgedacht. Trotzdem weiß ich nicht, ob mich das hier weiterbringt:

- Die Bitmap (fürs unsichtbare Zeichnen) wird im Form.Create erzeugt und ist als globale Variable verfügbar
- Diese Bitmap wird nur gelöscht, wenn sich deren Größe ändert - das passiert ja nur sehr selten
- D.h. die Bitmap müsste (nach Anlaufzeit) zu jeder beliebigen Zeit die richtigen Informationen halten, da sie ja nur überzeichnet und nicht komplett gelöscht wird
- Aus dem Paint-Event heraus nehm ich dann in einer Operation (Draw) diese Bitmap und kopiere sie auf die Komponente (PaintBox)
- D.h. ich zeichne aus meinem eigenen Thread heraus NICHT auf die Komponente

... Nachdem ich die oben genannten Punkte beachte, sollte nach meinem Kenntnisstand die Synchronisation kein Problem mehr sein. Offensichtlich liege ich falsch, wo genau muss ich noch nachbessern?

PS: Mit "Hauptthread" meint ihr die Eventqueue, oder?

@jaenicke: Die Bitmap erzeuge ich in der Create des Form, also aus der Eventqueue heraus, s.o.
@HenryHux: Ich gehe davon aus, dass ein einzelner Delphi-Aufruf auch atomar ist, d.h. eine Draw-Anweisung kann nicht mehr unterbrochen werden, dann dürfte das mit dem Threadsafe auch kein Problem sein, oder?

Stephan
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19339
Erhaltene Danke: 1752

W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Do 26.05.11 09:58 
user profile iconStephan.Woebbeking hat folgendes geschrieben Zum zitierten Posting springen:
- D.h. ich zeichne aus meinem eigenen Thread heraus NICHT auf die Komponente
Aber auf die Bitmap, oder? Also knallts eben, wie gesagt.

Ob du dir jetzt die Leinwand der Bitmap oder die deiner PaintBox zerschießt, ist doch egal, funktionieren tut es nicht mehr. Du musst den Zugriff auf die Bitmap synchronisieren.
Stephan.Woebbeking Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 97



BeitragVerfasst: Do 26.05.11 10:02 
Hab ich jetzt mal gemacht, die Zugriffe auf die Bitmap (d.h. das eigentliche Zeichnen sowie das Herauskopieren mit Draw) habe ich jetzt mit einer TCriticalSection.Acquire/Release geschützt, das hilft aber nix...?

edit: Auch Enter/Leave anstatt Acquire/Release macht keinen Unterschied. Wo ist mein Denkfehler???
Gausi
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 8553
Erhaltene Danke: 479

Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
BeitragVerfasst: Do 26.05.11 10:33 
CriticalSection bringt da nichts. Das sorgt nur dafür, dass keine zwei Trheads gleichzeitig daran rumwerkeln. Wenn du also von einem Thread aus eine CriticalSection betrittst, bleibst du im Kontext dieses Threads. Es wird nur dafür gesorgt, dass kein anderer Thread auch in diese CriticalSection eintreten darf.

Zeichnen auf ein Bitmap muss aber im Hauptthread erfolgen. Wenn du die Klasse TThread benutzt, geht da über Synchronize.

_________________
We are, we were and will not be.
Stephan.Woebbeking Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 97



BeitragVerfasst: Do 26.05.11 12:30 
Ja, in der Tat treffen wohl zwei Dinge zu:
1) Scheint es jetzt zu klappen (mal schauen wie lange...) ;)
2) Muss ich an meinen Hirnwindungen nochmal nachbessern...

Was genau tut denn jetzt Synchronize? Ich dachte die ganze Zeit es ginge darum zu verhindern, dass der Code "sich selbst überholt" wenn er zweimal aufgerufen wird? Das hätte ich doch mit der critical section erschlagen, die sollte doch auch verhindern, dass der Thread zweimal den gleichen Code anstartet, oder?

Stephan
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19339
Erhaltene Danke: 1752

W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Do 26.05.11 12:34 
Nein, hier geht es darum, dass der Code, der auf visuelle Elemente wie eine Bitmap oder Komponenten zugreift, im Kontext des Hauptthreads ablaufen muss.

Synchronize klinkt sich sozusagen in den Hauptthread ein, wenn der unterbrochen werden kann und führt den Code aus als würde es der Hauptthread tun.
Stephan.Woebbeking Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 97



BeitragVerfasst: Do 26.05.11 17:35 
Hm, ok, seh ich ein... Weisst du auch noch genauer, welche Informationen über diesen Kontext dann bezogen werden? Ich hätte ja gedacht die Bitmap ist nur eine Datenstruktur bei der es egal ist in welchem Thread sie bearbeitet wird, aber dem ist ja offensichtlich nicht so.

Ok, das nur noch zum Hintergrund, zum Verstehen, das Thema an sich ist damit gelöst, vielen Dank!

Stephan
Thom
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 70
Erhaltene Danke: 5


Delphi 10 Seattle Prof.
BeitragVerfasst: Fr 27.05.11 16:59 
user profile iconHenryHux hat folgendes geschrieben Zum zitierten Posting springen:
... jedoch sind TBitmaps nicht Threadsafe.

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Das geht mit Synchronisierung schon, aber die Bitmap muss auch im Kontext des Hauptthreads erzeugt worden sein.
Sinnvoller ist aber meistens, einfach die Ergebnisse per Event dem Hauptthread zu geben und den zeicnen zu lassen.

user profile iconGausi hat folgendes geschrieben Zum zitierten Posting springen:
Zeichnen auf ein Bitmap muss aber im Hauptthread erfolgen. Wenn du die Klasse TThread benutzt, geht da über Synchronize.

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Nein, hier geht es darum, dass der Code, der auf visuelle Elemente wie eine Bitmap oder Komponenten zugreift, im Kontext des Hauptthreads ablaufen muss.


Nein, nein, nein und nochmals nein.

Das ein typischer Fall von "Ich habe mal gehört, daß...". Diese weitverbreitete Meinung entspricht aber - zum Glück - nicht den Tatsachen: Sowohl Bitmaps und andere, von TGraphic abgeleitete Objekte und auch TBitmap32 können in Threads erstellt, verwendet und wieder freigegeben werden.

Das weiß ich deshalb so genau, weil ich in meinem aktuellen Projekt in 10 Threads gleichzeitig Bilder aus dem Internet lade, sie per Stream in TJPEGImage-Objekte schreibe, in TBitmap32-Bilder konvertiere und diese anschließend in einem speziellen Renderthread auf das eigentliche Pufferbitmap zeichne - und das alles völlig ohne Synchronisation mit dem Hauptthread! Lediglich beim Wechsel zwischen Hinter- und Vordergrundpuffer verwende ich TCriticalSection.

Was ist dabei zu beachten?

Ein TGraphic.Assign(TGraphic) ist tunlichst zu vermeiden: Zuweisungen sind im eigenen Quellcode zu erledigen, da Zugriffe auf TCanvas (sowohl Quelle als auch Ziel) synchronisiert werden müssen. Dazu gibt es die Methoden TCanvas.Lock und TCanvas.Unlock. Diese werden schon in der Hilfe von Delphi 5 beschrieben.
TJPEGImage besitzt eine Sonderstellung, da es intern ein TBitmap-Objekt besitzt. Dessen (!!!) Canvas ist während des Zeichenvorganges zu sperren.
TBitmap32 besitzt in der Methode TBitmap32.Assign(Source: TPersistent) einen Fehler: Dort wird ein temp. Canvas nicht gesperrt.

Im konkreten Fall würde ich zwei Pufferbitmaps verwenden: Einen Backpuffer, in dem der Thread hineinzeichnet und einen Frontpuffer, den die Routine der PaintBox verwendet.
Ist der Zeichenthread fertig, wird zwischen Front- und Backpuffer umgeschaltet - geschützt durch eine CriticalSection.
Zum Zeichnen in den Backpuffer ist der Canvas zu sperren.

Nichts gegen Hilfsbereitschaft und dem Drang, auf möglichst viele Fragen antworten zu wollen: Aber bitte nicht fachlich falsch... :wink:

Für diesen Beitrag haben gedankt: Martok
Stephan.Woebbeking Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 97



BeitragVerfasst: Mo 30.05.11 10:31 
Hmm, so wie beschrieben hatte ich das zeitweise (denke ich jedenfalls) realisiert. Ich habe nicht wirklich zwischen zwei Puffern hin- und hergeschaltet, sondern ich habe den Hintergrundpuffer in timeslots von einem Thread neu zeichnen lassen und nach zeichnen ein Signal gesetzt. Über das timed/OnPaint event habe ich dann den Hintergrundpuffer auf die Komponente kopieren lassen; das Ganze von der TCriticalSection gesperrt. Das hat dann ein paar Sekunden geklappt, dann war das Bild wech, d.h. obwohl alle Quellen korrekt durchlaufen wurden ist der Draw Aufruf ohne Effekt geblieben, was könnte der Grund dafür sein? Optisch sah es für mich tatsächlich nach etwas gravierend Defektem für mich aus, also war der Schluss mit einem "Verschlucken" (genauer möchte ich das gar nicht umschreiben, weil ich gar nicht in die Tiefen vorgedrungen bin um festzustellen an welcher Stelle der konkurrierende Zugriff etwas durcheinander bringt) der Threads sehr naheliegend?

Stephan
Thom
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 70
Erhaltene Danke: 5


Delphi 10 Seattle Prof.
BeitragVerfasst: Mo 30.05.11 11:45 
Dumme Frage: Weshalb nutzt Du überhaupt einen Thread zum Zeichnen, wenn Du sowieso nur abwechselnd zeichnest/anzeigst? Im ungünstigsten Fall blockiert OnPaint durch CriticalSection die Anwendung so lange, bis das Zeichnen beendet ist. Damit ist der Vorteil eines Multithreadprogrammes hinüber, da sich die verschiedenen Aufgaben trotzdem gegenseitig im Weg stehen.

Da zwei Threads nicht gleichzeitig auf die Zeichenfläche zugreifen dürfen, ist es unabdingbar, (mindestens) zwei Puffer zu verwenden. Dieses Prinzip wird zum Beispiel bei 3D-Programmen angewendet.

Wichtig ist beim Zugriff des Threads auf einen Canvas folgende Konstruktion:
ausblenden volle Höhe 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:
procedure TForm1.ZeichnenImThread;
var
  Puffer: TBitmap;
begin
  Hintergrundpuffer.Canvas.Lock;
  try
    //Zeichnen, kann auch länger dauern
    //...
    //Wird ein anderes Bild benutzt, ist auch dessen Canvas zu sperren:
    MeinBitmap.Canvas.Lock;
    try
      Hintergrundpuffer.Canvas.Draw(0,0,MeinBitmap);
    finally
      MeinBitmap.Canvas.Unlock;
    end;
  finally
    Hintergrundpuffer.Canvas.Unlock;
  end;
  //Umschalten zwischen Vorder- und Hintergrundpuffer:
  Puffer:=Vordergrundpuffer;
  //Nur für den kurzen Moment des Wechsels CriticalSection einsetzen, 
  //um die Blockierung so kurz wie möglich zu halten:
  CriticalSection.Enter;
  try
    Vordergrundpuffer:=Hintergrundpuffer;
  finally
    CriticalSection.Leave;
  end;
  Hintergrundpuffer:=Puffer;
  //Hier eventuell noch ein Neuzeichnen veranlassen:
  PaintBox.Invalidate;
  //Falls es damit Probleme gibt, die Windows-Funktion benutzen:
  //InvalidateRect(PaintBox.WindowHandle,nil,false);
end;

procedure TForm1.PaintBoxOnPaint(...);
begin
  CriticalSection.Enter;
  try
    PaintBox.Canvas.Draw(0,0,Vordergrundpuffer);
  finally
    CriticalSection.Leave;
  end;
end;
Stephan.Woebbeking Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 97



BeitragVerfasst: Di 31.05.11 09:31 
Gibt keine dummen Fragen, nur... ;)

Ne, schon ok, weitere Fragen/Anmerkungen:

1) Einen Thread nutze ich, weil die Applikation sonst alle paar Sekunden (ich zeichne nicht so oft, weil die Daten nicht x-mal/s geändert werden) einfriert.
2) Blockiert wird das OnPaint (mittlerweile) Ereignis nicht mehr, sondern die Zeichenroutine wird nur über Synchronize() aufgerufen. Deshalb nerv ich hier ja noch, weil ich mir die Hintergründe noch mehr erschliessen will. ;)
3) Warum hast du in deinem Beispiel DREI Puffer? Hintergrund, Vordergrund und PaintBox? Ich nutze die Komponente (auch PaintBox) als Vordergrundpuffer. Warum schlecht/gut, warum mit dem zusätzlichen?

Dein Ansatz ist tatsächlich grundsätzlich anders; fairerweise muss ich sagen, ich bin nicht in der Lage einen vernünftigen Vergleich zu ziehen zu meiner Synchronize() Lösung...? Ist wohl noch etwas Forschungsarbeit meinerseits zu investieren...

Stephan
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19339
Erhaltene Danke: 1752

W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Di 31.05.11 09:42 
Das was die Anwendung einfriert, ist doch aber der Datenabruf, oder? Denn dann reicht es doch, den in den Thread zu packen.
Thom
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 70
Erhaltene Danke: 5


Delphi 10 Seattle Prof.
BeitragVerfasst: Di 31.05.11 14:12 
Hallo Stephan,

die PaintBox ist kein bzw. besitzt keinen Puffer. Sie dient lediglich dazu, nach Auforderung von Windows in Form einer WM_PAINT-Nachricht möglicht sofort und live ihren Inhalt - in der Regel - in Richtung Grafikkarte auf den Weg zu schicken. Delphi leitet diese Nachricht über etliche Zwischenstufen auf die OnPaint-Methode um.
Diese Aufforderung zum Neuzeichnen kann irgendwann erfolgen: Zum Beispiel auch dann, wenn das eigene Fenster am Bildschirmrand war und wieder "hervorgezogen" wird oder wenn ein anderes Fenster die PaintBox - auch teilweise - verdeckt hatte und jetzt wieder "aufgedeckt" wird.

Dauert jetzt die Zeichenroutine zu lange, wird die komplette Nachrichtenverarbeitung der Anwendung blockiert - eben so lange, bis OnPaint fertig ist. Dabei werden schon Verzögerungen von mehr als 100 ms vom Anwender als unangenehm empfunden, da dann das Gefühl entsteht, die Anwendung reagiere zäh und träge.

Benutzt Du also nur einen Puffer, muß die OnPaint-Methode warten, bis der Zeichenthread fertig ist. Das ist das Problem.
Verwendest Du aber zwei getrennte Puffer, kann der Zeichenthread sich auch mal einige Sekunden Zeit lassen - OnPaint nimmt dann eben den schon fertig gezeichneten Vordergrundpuffer. Und das kann beliebig oft geschehen - bis der Renderthread mit dem Hintergrundpuffer fertig ist und die Puffer vertauscht.

Benutzt Du jetzt Synchronize, gibt es zwei Möglichkeiten:
  • Du zeichnest das Bitmap komplett im Hauptthread (also in der Methode, die von Synchronize aufgerufen wird) und blockierst damit die Anwendung genauso lang, wie die Darstellung dauert. Der Thread wird damit völlig überflüssig.
  • Du zeichnest im Thread und teilst der Anwendung nur über Synchronize mit, daß der Puffer fertig ist. Das geht allerdings nur solange gut, wie kein "ungeplanter" Aufruf von OnPaint erfolgt - denn dann muß der Hauptthread wieder warten, bis der Zeichenthread die CriticalSection verläßt. Auch damit ist der Sinn des Zeichenthreads fragwürdig, da er die Anwendung blockiert.

Für eine flüssige und verzögerungsfreie Darstellung bei komplexen Zeichenroutinen, die länger als etwa 100 bis 200 ms dauern, führt also wirklich kein (mir bekannter) Weg an der Verwendung von Threads und einem doppelten Puffer vorbei.
Stephan.Woebbeking Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 97



BeitragVerfasst: Di 31.05.11 17:13 
Ich hätt nicht fragen sollen... Jetzt versteh ich ehrlich immer weniger. Es ist so wie du in Option 1 beschreibst, Synchronize() ruft einfach aus dem Thread heraus die Zeichenroutine auf, die durchaus mehrere Sekunden zeichnen mag. Trotzdem stelle ich keinerlei Verzögerung fest. Halt, was ich natürlich tue ist, dass ich eine Markierung vornehme, sobald die Zeichnung fertig ist, erst dann wird das übertragen. Solange bleibt das Bild erhalten, welches voriges Mal gezeichnet wurde. Nun müsste ich doch aber wenigstens beim Wiederherstellen etc. des Fensters Verzögerungen feststellen? Nix...???

Nächstes Problem: Ein anderes Formular, hab ja gedacht ich hätt das jetzt halbwegs kapiert, da kommt's noch dicker. Hab zwei Paintboxen, die haben jetzt gar nix miteinander zu tun. Das Display soll nach Aktivierung per sekunde aktualisiert werden (aus dem Event, das dauert jetzt nicht so lang) und die Skala wird nur aus dem "natürlichen" OnPaint heraus gezeichnet, ändert sich ja nicht. Jetzt kommts; immer wenn das Display über Invalidate (oder Repaint, ändert nix) den getimten Zeichenbefehlt bekommt erlischt meine Skala... haben wie gesagt an sich gar nix miteinander zu tun...??? Woran könnte das liegen? Hab auch versucht die beiden mit Canvas.Lock/Unlock zu kontrollieren; macht aber m.E. keinen Sinn weil ich eben das nebenläufige gar nicht auf diesem Formular habe...?

Stephan
Thom
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 70
Erhaltene Danke: 5


Delphi 10 Seattle Prof.
BeitragVerfasst: Di 31.05.11 20:27 
Du müßtest Dich wahrscheinlich mit den Grundlagen von Threads beschäftigen und entsprechende Literatur dazu lesen. Michael Puff hat dazu eine gute Zusammenfassung geschrieben.

Wie lange programmierst Du schon? Nutzt Du Delphi für Hobbyzwecke oder professionell? Hast Du schon größere Programm geschrieben? Da ich das nicht weiß, versuche ich mal, ein paar Grundlagen zu erklären. Entschuldige bitte, wenn ich jetzt ins andere Extrem falle, und es möglicherweise zu primitiv mache.

Eine Anwendung besitzt mindestens einen Thread. Dieser erstellt die verwendeten Fenster, verarbeitet in einer Endlosschleife alle eintreffenden Nachrichten (Mausklicks, Tastenbetätigungen usw.) und räumt zu Schluß alles wieder auf. Um derartige Details muß man sich in der Regel bei der Programmierung mit Delphi nicht kümmern - das macht die VCL. Dieser Komfort hat aber seinen Preis: Man braucht sich dazu nur einmal die Größe eines compilierten Programmes anzuschauen, ohne daß man eine einzige Programmzeile geschrieben hat.

Aber wie im richtigen Leben auch kann dieser Hauptthread nur eine Aufgabe zur selben Zeit vernünftig ausführen. Dauert also eine Berechnung etwas länger, ist der Rest des Programmes nicht mehr bedienbar: Das Fenster läßt sich in der Regel nicht mehr verschieben, Klicks auf Buttons bleiben ohne Wirkung, Menüs bleiben geschlossen.
Schon unter Windows 98 und davor gab es die Möglichkeit, daß eine Anwendung zusätzlich zum Hauptthread weitere Threads startet, um "gleichzeit" andere Aufgaben erledigen zu können. Gleichzeitig war das nicht wirklich, da ein Thread immer unterbrochen werden mußte, damit ein anderer an die Reihe kommen konnte. Dennoch hatte man das Gefühl, daß eine Anwendung flüssiger arbeitete.
Zum Glück gibt es heute Hyperthreading und Prozessoren mit mehreren Kernen, so daß es jetzt möglich ist, wirklich mehrere Dinge quasi gleichzeitig zu machen.

In Deinem Fall würde sich der Hauptthread um Tasteneingaben und Mausklicks kümmern oder auch um die Ausgabe der PaintBox, währen ein zusätzlicher Thread sich um die grafische Aufarbeitung der Daten kümmert. So weit - so gut. Nun nutzt es aber nichts, wenn beide ohne Absprache nebeneinander vor sich hinwerkeln - die Arbeit muß abgesprochen, also synchronisiert werden. Die Methode Synchronize ist eine Möglichkeit dafür.
Intern sieht das so aus: Der Thread ruft Synchronize auf, Delphi sendet eine Nachricht, die im Haupthread empfangen wird und die den Aufruf der in Synchronize übergebenen Methode veranlaßt. Während der Hauptthread (!!!) die Aufgabe ausführt, wird der aufrufende Thread angehalten - also quasi schlafen gelegt. Das wäre im realen Leben äußerst ungerecht und erhöht auch im Programm keinesfalls die Produktivität: Wärend der Hauptthread Dein Bitmap erstellt, ist das Programm nicht mehr bedienbar. Probier das mal aus: Du wirst staunen... Klar: Wenn Du mit dem Programm nichts machst, stellt Du das auch nicht fest.

Du schreibst, daß das Bild in der PaintBox erhalten bleibt. OK: Dann wische mal mit einem anderen Fenster (zum Beispiel dem Taskmanager) darüber und schau, was passiert...

Genau das ist der Grund, weshalb die komplette, zeitintensive Arbeit im Zeichenthread (!!!) ausgeführt werden soll. Nichts mit Synchronize und Co.. Genau aus diesem Grund hatte ich etliche Beiträge weiter vor geschrieben, daß die Tips einiger anderer Forenmitglieder in diesem Zusammenhang leider unpraktisch sind und habe Dir das fertige Gerüst für Dein Programm gegeben.

Wegen Deines zweiten Problems mit den zwei PaintBoxen: Poste mal bitte Deinen Quelltext.