Je hinzugefügt einen TTimer zur Anwendung, nur um festzustellen, dass das Ereignis ausgeführt wird ist nicht, weil der VCL-Hauptthread beschäftigt ist?
Delphi-Entwickler | September 2000 |
Copyright Pinnacle Publishing, Inc. Alle Rechte vorbehalten.
Führt der Real Timer bitte?Steve ZimmelmanNotwendigkeit ist oft die Mutter der Erfindung, und dieses Mal war keine Ausnahme. Während ich auf einige Microsoft Word/Excel-Integration gearbeitet wurde, entstand ein Problem Wenn ich verarbeiten von Dokumenten mit der Visible-Eigenschaft auf False festgelegt wurde. Word würde Anzeigen eines Dialogfelds (die der Benutzer nicht sehen konnte) und warten auf eine Antwort. Nun, machte natürlich dies die Anwendung erscheinen, als ob es hing, war. Ich brauchte eine Möglichkeit zur Zeit des Prozesses und Dinge, zu behandeln, sie zu lange dauern sollte. Ich schlug eine TTimer-Komponente auf dem Formular, wählen Sie das Intervall für 1.000 (eine Sekunde), und gezählt wie oft das Timer-Ereignis ausgeführt. Klingt gut, oder? Falsch! Wenn das Dialogfeld Word selbst zeigten, war es den VCL-Hauptthread benutze, der Möglichkeit, andere Prozesse, einschließlich TTimer wiederum gebe war nicht. Also, noch einmal die Anwendung erschien als ob es hing, war.
Die Lösung war, einen Thread zu erstellen, der die Zeit, egal wie beschäftigt der VCL-Hauptthread war verfolgen konnte. Aber wie die beste Lösung aus objektorientierte Sicht offenbar keinen Thread zu erstellen, jedes Mal, wenn ich diese Funktionalität benötigt. Also habe ich eine neue Timer-Komponente, die erstellen und verwenden eine eigene Innengewinde und eine Ereignismethode in einem bestimmten Intervall ausführen.
Die TThreadTimer-Komponente ist das Ergebnis. Es ist eine einfache Unterklasse von TComponent, die ein Innengewinde verwaltet. Der Thread wird erstellt und zerstört durch die Komponente. Wenn die Enabled-Eigenschaft auf true festgelegt ist, wird der Thread erstellt und der Wert false, wird es zerstört. Ich wollte auch sicherstellen, dass der Thread nicht im Entwurfsmodus erstellt werden. So dass ich die ComponentState vor der Ausführung keine Methoden überprüft:
Prozedur TThreadTimer.SetEnabled(Value:Boolean);
BEGIN
Wenn (Wert <> FEnabled) dann beginnen
FEnabled: = Value;
Nicht erstellen oder den Thread zu töten, es sei denn
die app läuft.
Wenn nicht (CsDesigning In ComponentState)
Dann beginnen
Wenn FEnabled dann beginnen
FTimerEventCount: = 0;
FThread: = TTheThread.Create(Self);
End Else Begin
KillThread;
Ende;
Ende;
Ende;
Ende;
Ich habe eine Methode namens KillThread den Thread ausführen zu stoppen und befreit wird. Bevor ich den Thread tatsächlich zerstören kann, muss ich sicher sein, dass das zugeordnete Ereignis in der Mitte die Verarbeitung nicht. Wenn der Thread freigegeben wurde, während das Ereignis ausgeführt wurde, würde eine Ausnahme ausgelöst, da das Ereignis zu einem Thread zurückkehren würde, die nicht mehr vorhanden waren. Ich behandelt dies mit einer einfachen booleschen Variablen, FOnTimerEventInProgress, die auf True initialisiert haben, bevor das Timer-Ereignis ausgeführt wurde, und zu False gleich nach es fertig wechselte:
Prozedur TThreadTimer.KillThread;
BEGIN
Versuchen Sie
FEnabled: = False;
Wenn (FThread <> Nil) dann beginnen
Warten Sie auf das OnTimerInterval-Ereignis
Beenden Sie vor dem Beenden des Threads.
Während FThread.FOnTimerEventInProgress
Fangen
Application.ProcessMessages;
Ende;
FThread.Terminate;
FThread.Free;
Ende;
Schließlich
FThread: = Nil;
FTimerEventCount: = 0;
Ende;
Ende;
Da die Komponente ein Threadobjekt umfasst und das Thread-Objekt auf einige der Eigenschaften und Methoden der Komponente zu verweisen muss, habe ich den Konstruktor erstellen des Threads, um einen Verweis auf seinen Besitzer zu erfassen:
Konstruktor TTheThread.Create(AOwner:TThreadTimer);
BEGIN
FOnTimerEventInProgress: = False;
Wir müssen einige des Besitzers zugreifen
Eigenschaften aus dem Thread.
FOwner: = AOwner;
Geerbte Create(False);
Ende;
Das Threadobjekt selbst ist recht einfach. Es hat einen Konstruktor erstellen, eine Execute und die OnTimer-Methode. Die Execute-Methode führt nur nach dem Konstruktor erstellen und bleibt aktiv, bis die Execute-Methode beendet. Früher habe ich die Eigenschaft Terminated halten den Thread am Leben, bis es explizit gesagt hat, um zu beenden. Beendet wird, auf True festgelegt, wenn der Thread Terminate-Methode aufgerufen wird. In der Schleife ausführen verwende ich eine einfache Sleep() den Thread anhalten und warten auf das angegebene Zeitgeberintervall. Ich benutze Application.ProcessMessages, um sicherzustellen, dass der Thread hat alle aktuellen Verarbeitungsinformationen vor der Ausführung des Timer-Ereignis. Die Hauptkomponente, die als SynchronizeEvent bezeichnet erfaßte eine Eigenschaft. Dies macht die TThreadTimer verhält sich wie eine Standardkomponente TTimer, indem Sie das Ereignis mit der VCL-Hauptthread synchronisieren:
Prozedur TTheThread.Execute;
BEGIN
Während nicht Self.Terminated fangen
Application.ProcessMessages;
Sleep(FOwner.Interval);
Application.ProcessMessages;
Wenn FOwner.SynchronizeEvent dann
Synchronize(OnTimer)
Sonst
OnTimer;
Ende;
Ende;
Prozedur TTheThread.OnTimer;
BEGIN
Versuchen Sie
Wenn Assigned(FOwner.FOnTimerInterval) dann beginnen
Wenn FOwner.Enabled dann beginnen
FOnTimerEventInProgress: = True;
Inc(FOwner.FTimerEventCount);
FOwner.FOnTimerInterval(FOwner);
Ende;
Ende;
Schließlich
FOnTimerEventInProgress: = False;
Ende;
Ende;
Es gibt drei wesentliche Teile der TThreadTimer-Komponente: der Thread, Intervall und die TimerEvent. Wie bereits erwähnt, wird der Thread erstellt und zerstört die Enabled-Eigenschaft. Als der Thread erstellt wird, wird der Timer ist aktiv und führt das OnTimerInterval-Ereignis nach Ablauf der angegebenen Intervall hat.
Es gibt auch eine Funktion namens TimerEventCount-It tut genau das, was der Name schon sagt. Es erhöht eine ganze Zahl, die das OnTimerInterval-Ereignis verarbeitet wird. Dies kann verwendet werden, für potenzielle Timeouts zu sehen. Beispielsweise müssen Sie möglicherweise das Intervall für 1.000 (eine Sekunde) festgelegt, und legen Sie die Ereignis-Falle für TimerEventCount > = 60. Also, wenn das Ereignis 60mal ausführt, dann eine Minute ist vorbei und möglicherweise müssen Sie etwas in der Anwendung zu behandeln. Aber denken Sie daran, dieser Zeitgeber auf einem separaten Thread, so dass andere VCL-Objekte im Hauptthread zu manipulieren versucht Ausnahmen führen könnte.
Nach dem Erstellen des ThreadTimer-Objekts, dauerte es nicht lange, zu erkennen, dass das Timer-Ereignis nicht in der Lage wäre, alle VCL-Objekte zu aktualisieren, während der Hauptthread beschäftigt war. Wollte man eine Datensatzzeiger einer Tabelle überprüfen, während die Tabelle verarbeitet wurde und die Ergebnisse zu einem TLabel, würden Sie beispielsweise kein Glück sein. TLabel würde warten, bis der Hauptthread es erlaubt zu aktualisieren. Also zurück Zeichnung zu den sprichwörtlichen Vorstand wieder.
Ich entdeckte, dass TCanvas, nicht zum größten Teil den Haupt-Thread für die Aktualisierung verwendet. Also habe ich eine neues Label-Komponente namens TCanvasLabel. TCanvasLabel ist eine Unterklasse von TGraphicControl und verwendet nur die Canvas-Eigenschaft für das Zeichnen.
Ich entwarf TCanvasLabel, sodass es als eine Bezeichnung und eine ProgressBar verwendet werden könnte. Die Eigenschaften Fill, FillPercent und FillColor werden verwendet, um einen Teil von dem Label Canvas eine andere Farbe malen. Die Paint-Methode wird verwendet, um den Inhalt des Etiketts anzuzeigen:
Prozedur TCanvasLabel.Paint;
Var
Rect: TRect;
fLeft, fTop: Integer;
BEGIN
AutoSize festgelegt werden sollte, zu False, wenn das Etikett
in einem anderen Thread als Main wird
VCL-Thread.
Wenn AutoSize dahin
SetSize;
Rect: = ClientRect;
Etikett mit primären Hintergrundfarbe zu malen.
Canvas.Brush.Style: = BsSolid;
Canvas.Brush.Color: = Self.Color;
Canvas.FillRect(ClientRect);
Wenn (FillPercent > 0) und FFill Then Begin
Berechnen Sie den Prozentsatz der Füllung.
Rect.Right: = Trunc ((Rect.Right *
(FillPercent / 100))) ;
Canvas.Brush.Color: = FillColor;
Canvas.FillRect(Rect);
Ende;
Canvas.Brush.Style: = BsClear;
RS Ausrichtung des
TaLeftJustify:
BEGIN
fLeft: = 0;
Ende;
TaRightJustify:
BEGIN
fLeft: = ClientRect.Right -
Canvas.TextWidth(Caption);
Ende;
TaCenter:
BEGIN
fLeft: = Trunc ((ClientRect.Right -
Canvas.TextWidth(Caption))/2);
Ende;
Ende;
RS Layout
TlTop: fTop: = 0;
TlCenter:
fTop: = Trunc ((ClientRect.Bottom -
Canvas.TextHeight(Caption))/2);
TlBottom:
fTop: = (Self.Height -
Canvas.TextHeight(Caption));
Ende;
Canvas.TextOut(fleft,fTop,Caption);
erzwingen Sie die Leinwand selbst aktualisieren.
Canvas.Refresh;
Ende;
Es gibt ein paar Vorsichtsmaßnahmen müssen Sie aufpassen, wenn Ereignisse verwenden, die Verwendung von Threads:
- Die AutoSize-Eigenschaft des TCanvasLabel sollte auf False festgelegt werden, wenn die Anwendung ausgeführt. Größenänderung der Bezeichnung verwendet den VCL-Hauptthread, sodass das Label nicht aktualisiert.
- Achten Sie darauf, dass Sie keine standard VCL-Objekte in Ihrem Thread-Ereignis enthalten, ohne gründlich testen, oder sie speziell im Code innerhalb des Threads herzustellen.
Eine einfache Verwendung von diesen beiden Komponenten finden Sie in der Demo, die in der beigefügten Datei enthalten. Die einfache Anwendung veranschaulicht
der Unterschied zwischen einer Norm TTimer und den Timer in der TThreadTimer-Komponente gefunden.
Das Ereignis TTimer aktualisiert eine TLabel mit der aktuellen Zeit einmal pro Sekunde. Um einen anstrengenden Prozess erstellen, fügt die Anwendung 10.000 Datensätze in einer For-Schleife.
Sie werden bemerken, dass während des Vorgangs anfügen die Zeitanzeige für die Norm TTimer stoppt, während die anderen Etiketten in ihrer festgelegten Intervallen aktualisiert werden.
Multi-threaded-Timer
Multi-threaded-Timer : Mehreren tausend Tipps, um Ihr Leben einfacher machen.
Je hinzugefügt einen TTimer zur Anwendung, nur um festzustellen, dass das Ereignis ausgeführt wird ist nicht, weil der VCL-Hauptthread beschäftigt ist?
Delphi-Entwickler | September 2000 |
Copyright Pinnacle Publishing, Inc. Alle Rechte vorbehalten.
Führt der Real Timer bitte?Steve ZimmelmanNotwendigkeit ist oft die Mutter der Erfindung, und dieses Mal war keine Ausnahme. Während ich auf einige Microsoft Word/Excel-Integration gearbeitet wurde, entstand ein Problem Wenn ich verarbeiten von Dokumenten mit der Visible-Eigenschaft auf False festgelegt wurde. Word würde Anzeigen eines Dialogfelds (die der Benutzer nicht sehen konnte) und warten auf eine Antwort. Nun, machte natürlich dies die Anwendung erscheinen, als ob es hing, war. Ich brauchte eine Möglichkeit zur Zeit des Prozesses und Dinge, zu behandeln, sie zu lange dauern sollte. Ich schlug eine TTimer-Komponente auf dem Formular, wählen Sie das Intervall für 1.000 (eine Sekunde), und gezählt wie oft das Timer-Ereignis ausgeführt. Klingt gut, oder? Falsch! Wenn das Dialogfeld Word selbst zeigten, war es den VCL-Hauptthread benutze, der Möglichkeit, andere Prozesse, einschließlich TTimer wiederum gebe war nicht. Also, noch einmal die Anwendung erschien als ob es hing, war.
Die Lösung war, einen Thread zu erstellen, der die Zeit, egal wie beschäftigt der VCL-Hauptthread war verfolgen konnte. Aber wie die beste Lösung aus objektorientierte Sicht offenbar keinen Thread zu erstellen, jedes Mal, wenn ich diese Funktionalität benötigt. Also habe ich eine neue Timer-Komponente, die erstellen und verwenden eine eigene Innengewinde und eine Ereignismethode in einem bestimmten Intervall ausführen.
Die TThreadTimer-Komponente ist das Ergebnis. Es ist eine einfache Unterklasse von TComponent, die ein Innengewinde verwaltet. Der Thread wird erstellt und zerstört durch die Komponente. Wenn die Enabled-Eigenschaft auf true festgelegt ist, wird der Thread erstellt und der Wert false, wird es zerstört. Ich wollte auch sicherstellen, dass der Thread nicht im Entwurfsmodus erstellt werden. So dass ich die ComponentState vor der Ausführung keine Methoden überprüft:
Prozedur TThreadTimer.SetEnabled(Value:Boolean);
BEGIN
Wenn (Wert <> FEnabled) dann beginnen
FEnabled: = Value;
Nicht erstellen oder den Thread zu töten, es sei denn
die app läuft.
Wenn nicht (CsDesigning In ComponentState)
Dann beginnen
Wenn FEnabled dann beginnen
FTimerEventCount: = 0;
FThread: = TTheThread.Create(Self);
End Else Begin
KillThread;
Ende;
Ende;
Ende;
Ende;
Ich habe eine Methode namens KillThread den Thread ausführen zu stoppen und befreit wird. Bevor ich den Thread tatsächlich zerstören kann, muss ich sicher sein, dass das zugeordnete Ereignis in der Mitte die Verarbeitung nicht. Wenn der Thread freigegeben wurde, während das Ereignis ausgeführt wurde, würde eine Ausnahme ausgelöst, da das Ereignis zu einem Thread zurückkehren würde, die nicht mehr vorhanden waren. Ich behandelt dies mit einer einfachen booleschen Variablen, FOnTimerEventInProgress, die auf True initialisiert haben, bevor das Timer-Ereignis ausgeführt wurde, und zu False gleich nach es fertig wechselte:
Prozedur TThreadTimer.KillThread;
BEGIN
Versuchen Sie
FEnabled: = False;
Wenn (FThread <> Nil) dann beginnen
Warten Sie auf das OnTimerInterval-Ereignis
Beenden Sie vor dem Beenden des Threads.
Während FThread.FOnTimerEventInProgress
Fangen
Application.ProcessMessages;
Ende;
FThread.Terminate;
FThread.Free;
Ende;
Schließlich
FThread: = Nil;
FTimerEventCount: = 0;
Ende;
Ende;
Da die Komponente ein Threadobjekt umfasst und das Thread-Objekt auf einige der Eigenschaften und Methoden der Komponente zu verweisen muss, habe ich den Konstruktor erstellen des Threads, um einen Verweis auf seinen Besitzer zu erfassen:
Konstruktor TTheThread.Create(AOwner:TThreadTimer);
BEGIN
FOnTimerEventInProgress: = False;
Wir müssen einige des Besitzers zugreifen
Eigenschaften aus dem Thread.
FOwner: = AOwner;
Geerbte Create(False);
Ende;
Das Threadobjekt selbst ist recht einfach. Es hat einen Konstruktor erstellen, eine Execute und die OnTimer-Methode. Die Execute-Methode führt nur nach dem Konstruktor erstellen und bleibt aktiv, bis die Execute-Methode beendet. Früher habe ich die Eigenschaft Terminated halten den Thread am Leben, bis es explizit gesagt hat, um zu beenden. Beendet wird, auf True festgelegt, wenn der Thread Terminate-Methode aufgerufen wird. In der Schleife ausführen verwende ich eine einfache Sleep() den Thread anhalten und warten auf das angegebene Zeitgeberintervall. Ich benutze Application.ProcessMessages, um sicherzustellen, dass der Thread hat alle aktuellen Verarbeitungsinformationen vor der Ausführung des Timer-Ereignis. Die Hauptkomponente, die als SynchronizeEvent bezeichnet erfaßte eine Eigenschaft. Dies macht die TThreadTimer verhält sich wie eine Standardkomponente TTimer, indem Sie das Ereignis mit der VCL-Hauptthread synchronisieren:
Prozedur TTheThread.Execute;
BEGIN
Während nicht Self.Terminated fangen
Application.ProcessMessages;
Sleep(FOwner.Interval);
Application.ProcessMessages;
Wenn FOwner.SynchronizeEvent dann
Synchronize(OnTimer)
Sonst
OnTimer;
Ende;
Ende;
Prozedur TTheThread.OnTimer;
BEGIN
Versuchen Sie
Wenn Assigned(FOwner.FOnTimerInterval) dann beginnen
Wenn FOwner.Enabled dann beginnen
FOnTimerEventInProgress: = True;
Inc(FOwner.FTimerEventCount);
FOwner.FOnTimerInterval(FOwner);
Ende;
Ende;
Schließlich
FOnTimerEventInProgress: = False;
Ende;
Ende;
Es gibt drei wesentliche Teile der TThreadTimer-Komponente: der Thread, Intervall und die TimerEvent. Wie bereits erwähnt, wird der Thread erstellt und zerstört die Enabled-Eigenschaft. Als der Thread erstellt wird, wird der Timer ist aktiv und führt das OnTimerInterval-Ereignis nach Ablauf der angegebenen Intervall hat.
Es gibt auch eine Funktion namens TimerEventCount-It tut genau das, was der Name schon sagt. Es erhöht eine ganze Zahl, die das OnTimerInterval-Ereignis verarbeitet wird. Dies kann verwendet werden, für potenzielle Timeouts zu sehen. Beispielsweise müssen Sie möglicherweise das Intervall für 1.000 (eine Sekunde) festgelegt, und legen Sie die Ereignis-Falle für TimerEventCount > = 60. Also, wenn das Ereignis 60mal ausführt, dann eine Minute ist vorbei und möglicherweise müssen Sie etwas in der Anwendung zu behandeln. Aber denken Sie daran, dieser Zeitgeber auf einem separaten Thread, so dass andere VCL-Objekte im Hauptthread zu manipulieren versucht Ausnahmen führen könnte.
Nach dem Erstellen des ThreadTimer-Objekts, dauerte es nicht lange, zu erkennen, dass das Timer-Ereignis nicht in der Lage wäre, alle VCL-Objekte zu aktualisieren, während der Hauptthread beschäftigt war. Wollte man eine Datensatzzeiger einer Tabelle überprüfen, während die Tabelle verarbeitet wurde und die Ergebnisse zu einem TLabel, würden Sie beispielsweise kein Glück sein. TLabel würde warten, bis der Hauptthread es erlaubt zu aktualisieren. Also zurück Zeichnung zu den sprichwörtlichen Vorstand wieder.
Ich entdeckte, dass TCanvas, nicht zum größten Teil den Haupt-Thread für die Aktualisierung verwendet. Also habe ich eine neues Label-Komponente namens TCanvasLabel. TCanvasLabel ist eine Unterklasse von TGraphicControl und verwendet nur die Canvas-Eigenschaft für das Zeichnen.
Ich entwarf TCanvasLabel, sodass es als eine Bezeichnung und eine ProgressBar verwendet werden könnte. Die Eigenschaften Fill, FillPercent und FillColor werden verwendet, um einen Teil von dem Label Canvas eine andere Farbe malen. Die Paint-Methode wird verwendet, um den Inhalt des Etiketts anzuzeigen:
Prozedur TCanvasLabel.Paint;
Var
Rect: TRect;
fLeft, fTop: Integer;
BEGIN
AutoSize festgelegt werden sollte, zu False, wenn das Etikett
in einem anderen Thread als Main wird
VCL-Thread.
Wenn AutoSize dahin
SetSize;
Rect: = ClientRect;
Etikett mit primären Hintergrundfarbe zu malen.
Canvas.Brush.Style: = BsSolid;
Canvas.Brush.Color: = Self.Color;
Canvas.FillRect(ClientRect);
Wenn (FillPercent > 0) und FFill Then Begin
Berechnen Sie den Prozentsatz der Füllung.
Rect.Right: = Trunc ((Rect.Right *
(FillPercent / 100))) ;
Canvas.Brush.Color: = FillColor;
Canvas.FillRect(Rect);
Ende;
Canvas.Brush.Style: = BsClear;
RS Ausrichtung des
TaLeftJustify:
BEGIN
fLeft: = 0;
Ende;
TaRightJustify:
BEGIN
fLeft: = ClientRect.Right -
Canvas.TextWidth(Caption);
Ende;
TaCenter:
BEGIN
fLeft: = Trunc ((ClientRect.Right -
Canvas.TextWidth(Caption))/2);
Ende;
Ende;
RS Layout
TlTop: fTop: = 0;
TlCenter:
fTop: = Trunc ((ClientRect.Bottom -
Canvas.TextHeight(Caption))/2);
TlBottom:
fTop: = (Self.Height -
Canvas.TextHeight(Caption));
Ende;
Canvas.TextOut(fleft,fTop,Caption);
erzwingen Sie die Leinwand selbst aktualisieren.
Canvas.Refresh;
Ende;
Es gibt ein paar Vorsichtsmaßnahmen müssen Sie aufpassen, wenn Ereignisse verwenden, die Verwendung von Threads:
- Die AutoSize-Eigenschaft des TCanvasLabel sollte auf False festgelegt werden, wenn die Anwendung ausgeführt. Größenänderung der Bezeichnung verwendet den VCL-Hauptthread, sodass das Label nicht aktualisiert.
- Achten Sie darauf, dass Sie keine standard VCL-Objekte in Ihrem Thread-Ereignis enthalten, ohne gründlich testen, oder sie speziell im Code innerhalb des Threads herzustellen.
Eine einfache Verwendung von diesen beiden Komponenten finden Sie in der Demo, die in der beigefügten Datei enthalten. Die einfache Anwendung veranschaulicht
der Unterschied zwischen einer Norm TTimer und den Timer in der TThreadTimer-Komponente gefunden.
Das Ereignis TTimer aktualisiert eine TLabel mit der aktuellen Zeit einmal pro Sekunde. Um einen anstrengenden Prozess erstellen, fügt die Anwendung 10.000 Datensätze in einer For-Schleife.
Sie werden bemerken, dass während des Vorgangs anfügen die Zeitanzeige für die Norm TTimer stoppt, während die anderen Etiketten in ihrer festgelegten Intervallen aktualisiert werden.