![]() |
TimeSetEvent / Canvas / Thread?
Ich möchte eine wissenschaftliche Grafik kontinuierlich flüssig animieren und die restliche Benutzeroberfläche soll interaktiv bleiben und die Animation nicht hakeln lassen. Als Trivialversion habe ich daher erst einmal eine primitive "NewsTicker-Laufschrift" testen wollen, die echte Grafik sollte dann später leicht auszutauschen sein. Aber mir gelingt nicht einmal ansatzweise eine flüssige, pixelbasierte Animation. Wer hat Tipps, wie man da herangeht?
Ich dachte, dass ich das mit einem MultiMedia-Timer realisieren könnte. Die Schrift springt aber um geschätzte 10 px je sichtbarer Änderung und jede Mausbewegung auf der Form lässt alles stehen. Liegt das daran, dass das ganze nicht threadsicher ist? Was mache ich so grundsätzlich falsch und wie kann man das Problem beheben? (D6 unter W7)
Code:
und aufgerufen einfach durch die Form der Anwendung:
type
TNewsTicker = class(TObject) private mmResult : Integer; Canvas : TCanvas; X : Integer; public constructor Create(ACanvas: TCanvas); destructor Destroy; override; end; implementation procedure TimeCallBack(TimerID, Msg: Uint; dwUser, dw1, dw2: DWORD); pascal; var ANewsTicker : TNewsTicker; begin ANewsTicker := TNewsTicker(dwUser); ANewsTicker.Canvas.TextOut(ANewsTicker.X, 100, 'Test'); Inc(ANewsTicker.X); end; { TNewsTicker } constructor TNewsTicker.Create(ACanvas: TCanvas); begin inherited Create; Canvas := ACanvas; X := 0; mmResult := TimeSetEvent(50, 10, @TimeCallBack, DWORD(Self), TIME_PERIODIC); end; destructor TNewsTicker.Destroy; begin TimeKillEvent(mmResult); inherited Destroy; end;
Code:
procedure TFormMain.Button1Click(Sender: TObject);
begin MainNewsTicker := TNewsTicker.Create(Canvas); end; |
AW: TimeSetEvent / Canvas / Thread?
Zunächst könntest Du die Gemauigkeit auf 0 ( = höchste Genauigkeit ) setzen und die Aufrufhäufigkeit auf 10 mSec
also von mmResult := TimeSetEvent(50, 10, @TimeCallBack, DWORD(Self), TIME_PERIODIC); auf mmResult := TimeSetEvent(10, 0, @TimeCallBack, DWORD(Self), TIME_PERIODIC); herabsetzen. Wenn Du die Routine nur alle 50 mSec ausführen möchtest, kannst Du intern einen Zähler von 1 bis 5 installieren und nur bei Zähler = 5 ausführen. Dies löst aber wahrscheinlich nocht nicht das Problem, dass der Timer bei Mausbewegungen pausiert. Dieses Pausieren habe ich bei Disk I/O auch schon feststellen müssen uns suche nach einer entsprechenden Lösung. |
AW: TimeSetEvent / Canvas / Thread?
Du sprichst von Threadsicher im Zusammenhang mit einem Timer. Der Timer ist aber kein Thread! Dein Vorhaben würde ich nämlich tatsächlich mit einem (echten) Thread angehen. Mal eine Pseudoklasse:
Delphi-Quellcode:
type
TMyAnimation = class(TThread) private FBitmap: TBitmap; FTargetCanvas: TCanvas; procedure DrawFrame; protected procedure Execute; override; public constructor Create(aCanvas: TCanvas); destructor Destroy; override; end; implementation constructor TMyAnimation.Create(aCanvas: TCanvas); begin inherited Create(false); FTargetCanvas := aCanvas; FBitmap := TBitmap.Create; end; destructor TMyAnimation.Destroy; begin FBitmap.Free; end; procedure TMyAnimation.DrawFrame; begin FTargetCanvas.Draw(FBitmap.Canvas, ... ...); end; procedure TMyAnimation.Execute; var loopBeginTickCount: Int64; const MS_PER_FRAME = 50; begin repeat loopBeginTickCount := GetTickCount; // FBitmap mit neuem Frame bestücken, was auch immer das ist Synchronize(DrawFrame); Sleep(Max(MS_PER_FRAME - (GetTickCount-loopBeginTickCount), 1)); until Terminated; end; |
AW: TimeSetEvent / Canvas / Thread?
Nicht die Pixel an dem ungenauen Timer-Interval richten, denn Timer-Events haben eine geringe Priorität ... also nahezu alle anderen Messages werden bevorzugt verarbeitet und ist mal der Rechner und/oder dein VCL-Thread ausgelastet, dann hängt es halt.
Ein Intervall von 50 (bis zu 20 Bilder die Sekunde) sollte ausreichen, aber dabei nicht das X um 1 hochzählen, sondern die tatsächliche Zeit, zwischen den Aufrufen oder von Beginn an, messen und davon abhängig die Position "berechnen". [add] Ich würde garkeine Thread verwenden. Einfach den Timer und immer wenn Zeit ist, wird gezeichnet ... der Thread kann da auch nix machen, wenn keine Zeit vorhanden ist, da er sich ja eh synchronisieren muß. |
AW: TimeSetEvent / Canvas / Thread?
Ein ausgelastetes System ist ein ausgelastetes System. Klar. Aber ein Thread knallt doch regelmäßiger dazwischen als ein Timer, vor allem wenn man ihm noch ein wenig höhere Prio verpasst - falls die Animation diese Wichtigkeit hat. Streng genommen müsste man daher sogar 2 Thread nehmen: Einen, der die Frames berechnet, und einen der nur zeichnet. Da ich in obigem Beispiel aber die zum berechnen benötigte Zeit im Sleep() berücksichtige, geht das so auch schon ganz gut. (Ich hab's ziemlich genau so auch mehrfach im Einsatz. Das geht schon ganz gut, glaub mir ;))
|
AW: TimeSetEvent / Canvas / Thread?
Application.OnIdle ???
|
AW: TimeSetEvent / Canvas / Thread?
Feuert auf manchen Systemen nur, wenn man die Maus über einem der Fenster bewegt, oder man im OnIdle von Hand invalidiert, was wiederum zu Hakeln in der Bedienung führen kann. Vor allem, wenn das Erstellen der Bilder etwas mehr als eine halbe Hand voll Zyklen kostet.
|
AW: TimeSetEvent / Canvas / Thread?
;-) nuja, wenn man Done auf false setzt nicht ....
|
AW: TimeSetEvent / Canvas / Thread?
Okay, ja :oops: stimmt, das war damals als ich diverse Methoden probiert habe mein Fehler, Mist =) Dennoch finde ich es hübscher, möglichst wenig im Thread der GUI rennen zu lassen. Ich mag Threads mittlerweile sooo gerne, weil sie oft schon von sich aus zu schlankem und stark entkoppeltem Design zwingen. Und gerade bei so schnellen zyklischen Dingen wäre ich heilfroh das weitestgehend aus meinem GUI Kontext raus zu haben. Hat mir einfach jetzt schon zu oft das Leben leichter gemacht, als dass ich es nicht nachdrücklich empfehlen wollen würde :)
|
AW: TimeSetEvent / Canvas / Thread?
OnIdle wird, so wie es auch in der OH erwähnt wird, nur einmal ausgeführt, wenn alle anderen nstehenden Messages abgearbeitewt wurden.
Bewegt man die Maus ein Stückchen, dann kommen neue Messages rein (z.B. WM_MOUSEMOVE) und wurden diese wieder abgearbeitet, wird OnIdle jedes Mal erneut ausgeführt. Wie gesagt, um in Delphi auf die VCL-Forms zu zeichnen, muß/sollte man sich im VCL-Thread befinden ... also solange man nicht zusätzliche (längere) Berechnungen im Thread hat, kommt man mit einem Timer IMHO besser. |
AW: TimeSetEvent / Canvas / Thread?
Zitat:
Delphi-Quellcode:
sorgt dafür das das Event öfters kommt (solange bis man fertig ist und Done nicht mehr auf False setzt).
Done = False
|
AW: TimeSetEvent / Canvas / Thread?
Belastet "Sleep" nicht die CPU (while sleeping) ? Danke, werd's mal ausprobieren.
Delphi-Quellcode:
repeat loopBeginTickCount := GetTickCount; // FBitmap mit neuem Frame bestücken, was auch immer das ist Synchronize(DrawFrame); Sleep(Max(MS_PER_FRAME - (GetTickCount-loopBeginTickCount), 1)); until Terminated; |
AW: TimeSetEvent / Canvas / Thread?
Nein, Sleep versetzt den aufrufenden Thread im Scheduler in einen "schlafenden" Status, und er wird erst wieder erweckt wenn dessen Timeout (der übergebene Wert in ms) abgelaufen ist. So lange ein Thread schläft, tut er einfach gornix, ausser ein Eintrag (unter vielen) im Scheduler zu sein.
Himi und Bummi: Warum wölltet ihr einen Thread hier so gerne meiden? Es ist kaum mehr Code, und der einzige, dafür flotte Zugriff auf das VCL Formular ist abgesichert. Dafür ist man hübsch entkoppelt, und wird auch dann nicht in die Bedrängnis kommen den GUI Thread zuzukleistern, wenn das Bildberechnen doch mal üppiger wird. Timer haben für mich fast immer einen Beigeschmack von Gefrickel und "so lange es gut geht, Glück gehabt". Gerade bei so kurzen Zykluszeiten! Und SO viel einfacher zu debuggen ist eine mit Timern vollgekleisterte Anwendung auch nicht wirklich :) |
AW: TimeSetEvent / Canvas / Thread?
@Medium
Ich habe nichts gegen Thread, im Gegenteil, allerdings muss die Hauptarbeit auch im Thread stattfinden, da alle Canvaszugriffe Threadsave gemacht werden müssen macht es halt IMHO auch nur Sinn wenn nicht die Hauptarbeit darin besteht auf dem Canvas zu arbeiten. |
AW: TimeSetEvent / Canvas / Thread?
Ich hätte als Haupt"arbeit" hier angesehen, dass das (sehr kurze) Zeitintervall zwischen den Bildern möglichst konstant eingehalten wird. Dabei besteht die Arbeit natürlich hauptsächlich aus passend Warten, das ist wohl wahr :) Dafür erscheint mir der Scheduler aber durchaus auch zuverlässiger als die Messagequeue. Aber gut, bei so kleinen Aufgaben wirds dann fast mehr eine Glaubensfrage ob Thread oder Timer, wobei mir der Thread eben wegen der Flottheit des Intervalls zuverlässiger scheint. Letztlich ist das natürlich auch kein Garant, Windows ist ja (manchmal leider) kein RT OS.
|
AW: TimeSetEvent / Canvas / Thread?
Hmm...
Die Hauptarbeit wird sicherlich das zeichen sein. Allso zeichne im Thread auf eine TempBMP immer kontinuierlich und löse nur einen Event aus, wenn die Bitmap "fertig" ist. Über Synconize wird leider immer die Hauptthread angehalten, daher würde ich eine Sendmessage erzeugen und in dieser dann lediglich die TempBMP auf den Canvas kopieren... Grüsse Mavarik |
AW: TimeSetEvent / Canvas / Thread?
Würde ich im Normalfall auch so tun, aber gerade bei Animationen kann einem die Messagequeue durchaus den Spaß verderben. Und ob ich nun synchronized oder durch eine Message ausgelöst zeichne - in beiden Fallen wird die selbe Arbeit im Hauptthread gemacht. (Und ein Bitmap auf einen Canvas blitten ist nicht wirklich zeitraubend, so lange man jetzt nicht über Full-HD redet.)
Aber grundsätzlich würde ich, wenn es nicht auf die Millisekunde ankommt, auch Notifications in Form von Messages nutzen. (Mache ich auch fast überall in meinen Projekten so.) Daher ein sehr berechtigter Einwand. |
AW: TimeSetEvent / Canvas / Thread?
@Mavarik
mach Dir mal den Spass mehrere Threads gleichzeitig auf TempBitmaps malen zu lassen und diese Synchronized irgendwo darzustellen/laden. Ich hatte dabei 90% ausfälle bei den Bitmaps wenn kein Canvas.Lock verwendet wird, das letztlich fast mit dem Arbeiten im Haupthread gleichzusetzen ist ... |
AW: TimeSetEvent / Canvas / Thread?
Das ist aber ein anderes Problem! Ich wäre nichtmals auf die Idee gekommen, mehrere Threads in das selbe Bitmap malen zu lassen :shock:
Für so etwas würde ich mein Bitmap in N Streifen unterteilen, einen Verwaltungsthread die eigentlichen Renderthreads starten, in diesem auf dessen Fertigwerden warten, dann die Streifen im Verwaltungsthread auf ein kombiniertes Bitmap zeichnen, und DAS dann auf den Bildschirm bringen. So hat jeder fein säuberlich seine eigenen Ressourcen, und keiner muss irgendwen locken. (Auch schon so gemacht, für nen Mulitiline/threaded Raytracer.) |
AW: TimeSetEvent / Canvas / Thread?
Zitat:
Da werden dann noch über mehrerer Threads die Teile der BMP erst gepackt und dann auf einen Server gestreamt... ;-) Also denke ich ich weis wovon ich rede... :oops: Mavarik |
AW: TimeSetEvent / Canvas / Thread?
Wieso willst unbedingt einen Thread ?
Delphi-Quellcode:
Macht absolut nix innerhalb des Threads, sondern alle "Arbeit" wird in der VCL erledigt ... wozu also keinen Timer, statt des Sleeps und gleich in der VCL bleiben?
loopBeginTickCount := GetTickCount;
// FBitmap mit neuem Frame bestücken, was auch immer das ist Synchronize(DrawFrame); Sleep(Max(MS_PER_FRAME - (GetTickCount-loopBeginTickCount), 1)); |
AW: TimeSetEvent / Canvas / Thread?
Nochmal: Weil der Scheduler deutlich zuverlässiger ist was das Einhalten der "Schlafzeit" angeht, da man sich u.a. nicht noch die Messagequeue ans Bein bindet. Die scheint ja, wie der TE schrieb, durchaus etwas anfälliger für Einflüsse, die man nicht in der Hand hat. Das war ja das Problem des TE.
Zudem könnte die Kommentarzeile in der Realität durchaus einige dicke Operationen beinhalten, ich weiss ja nicht wie der TE seine Bilder erzeugt, bzw. woher er sie liest. |
AW: TimeSetEvent / Canvas / Thread?
@Mavarik
ich muss mich erst mal entschuldigen, ich hatte vor 2 Jahren eine Fehler beim verwenden von Bitmaps in Threads, der sich nur Threadreduzierung und Canvas-Lock beheben ließ, ich kann diesen Fehler nicht mehr reproduzieren. Er trat mit D2007 bei einer Entwicklung in einer VM auf, jeder Thread hatte seine eigenen Bitmaps die er bearbeitet hat, diese waren bei mehr als 5 Thread nachweislich zum großen Teil unbrauchbar ich habe es seinerzeit auf VCL-Probleme geschoben ..... |
AW: TimeSetEvent / Canvas / Thread?
Zitat:
|
AW: TimeSetEvent / Canvas / Thread?
Ich weiß es leider nicht, ich habe den Code gestern nochmals hervorgekramt neu Kompiliert und bekomme die Fehler nicht mehr, es war eine Verzeichnisvorschau für Bildordner wie Explorer/Kacheln nur etwas "apfeliger" mit 8 GDI+ verwendenden Threads ...
|
AW: TimeSetEvent / Canvas / Thread?
Schade...
Das sind immer die Sachen - da sucht man ewig dran und plötzlich ist es weg... Ätzend! Grüsse Mavarik |
Alle Zeitangaben in WEZ +1. Es ist jetzt 21:10 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