Announcement

Collapse
No announcement yet.

Beendigung von TThread

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • Beendigung von TThread

    Ich verwende einen Thread, der diverse Anzeigen im Hauptformular via Synchronize ausführt.
    Beim Beenden im OnCloseQuery-Ereignis des Hauptformulars rufe ich folgendes auf:

    Th.Terminate;
    Th.WaitFor;

    Manchmal hängt das Hauptprogramm nun an Th.WaitFor fest. Mit Hilfe des Debuggers habe ich herausgefunden, daß mein Thread in SendMessage hängt, also im Aufruf von Synchronize. Es scheint sich hier um einen klassischen Deadlock zu handeln, Der Thread wartet in Synchronize, daß er in die Botschaftsschleife des Hauptprogramms eintreten kann, Das Hauptprogramm wartet in der Botschaftsschleife auf die Beendigung des Threads.
    Ich habe auch schon die Methode Synchronize derart überschrieben, daß das ererbte Synchronize nur aufgerufen wird, wenn Terminated false ist, aber ohne Erfolg, der Deadlock bleibt.

    Wie kann man sowas verhindern?
    Andreas Richter

  • #2
    Hi

    Am effektivsten ist PostMessage() oder SendNotifyMessage() an Dein Formular (mit selbstdefinierter Message). Die Frage ist aber: muss der threaded Code unbedingt sofort abgearbeitet werden, oder reicht es das er fast synchrone abgearbeitet wird ? Mit PostMessage() kann Dein auszuführender Code manchmal erst verspätet bearbeitet werden. Im Allgemeinen erkennt man aber keine sichtbare Zeitverzögerung.

    Ich benutze .Synchronize meistens nie, entweder die PostMessage() Methode oder bei Zeichenoperationen ein Blocking im Critical Sections, wobei wenn .RTLCriticalSection.LockCount > 0 wird eben nichts gezeichnet.

    Gruß Hage

    Comment


    • #3
      Hallo Hallo,<br>mit großem Interesse habe ich hier folgenden Abschnit von Dir gelesen: <i>oder bei Zeichenoperationen ein Blocking im Critical Sections, wobei wenn .RTLCriticalSection.LockCount > 0 wird eben nichts gezeichnet. </i> Hättest Du da vielleicht mal ein kleines Beispiel?<br>:-) Jen

      Comment


      • #4
        Hi

        Vorsicht, es ist eine nicht dokumentierte Funktion.

        <pre>

        var
        FLock: TRTLCriticalSection;<br>

        procedure DoPaint;
        begin
        if FLock.LockCount = 0 then
        begin
        EnterCritialSection(FLock)
        try
        ...Paint dies und das
        finally
        LeavCriticalSection(FLock);
        end;
        end;
        end;<br>

        initialization
        InitializeCriticalSection(FLock);
        finalization
        DeleteCriticalSection(FLock);
        end.

        </pre>

        Gruß Hage

        Comment


        • #5
          Leider ist die ganze Applikation schon fertig, und Synchronize wird ziemlich oft verwendet. Außerdem benutze ich folgende Vorgehensweise, um z.B. in ein Memo im Hauptformular zu schreiben:

          Im Thread habe ich eine function, die mit einem String-Parameter aufgerufen wird, z.B.

          void __fastcall MyThread::AddLine(char* s)<br>
          {<br>
          StrParam= s;<br>
          Synchronize(DoAddLine);<br>
          }<br>
          <br>
          void __fastcall MyThread:oAddLine()<br>
          {<br>
          MainForm->Memo1->Add(StrParam);<br>
          }<br>
          <br>
          wobei der Pointer StrParam ein Member von MyThread ist. Wenn ich nun PostMessage verwende, kann es sein, daß der Thread den Pointer schon wieder verändert, während er noch im Hauptthread verwendet wird.
          <br>
          Ich wollte mal folgendes versuchen:
          Th->Terminate() in OnCloseQuery aufrufen, und CanClose auf false setzen, so daß das Hauptformular noch weiter angezeigt wird. Danach rufe ich Close() des Hauptformulars nochmal aus TMyThread::OnTerminate auf. So kann ich sicher sein, daß der Thread nun wirklich fertig ist.
          <br>
          Sorry, eigentlich schreibe ich mit dem C++Builder, aber ich stelle meine Fragen lieber im Delphi-Forum, da hier die Resonanz größer ist, die Komponenten sind schließlich die gleichen ;-)

          Comment


          • #6
            Hi

            Stimmt, asynchrone Aktualisierung ist in dem Fall ungeeignet. Du könntest aber versuchen den TMemo.AddLine code im Thread auszuführen. Wenn Du mal in die original Sourcen schaust, so wirst Du feststellen das an das Memo SendMessage()'s gesendet werden. Damit könntest Du Probleme die auf der VCL beruhen umgehen.

            Grundsätzlich gibt es aber mit SendMessage() aus Threads herraus aber immer Probleme, besonders wenn noch GDI Ausgaben mitmischen.

            Ich an Deiner Stelle würde .Synchronize umgehen:

            <pre>

            const
            wm_AddString = wm_User + xxx;<br>

            type
            TForm1 = class(TForm)
            Memo1: TMemo;
            protected
            procedure WMAddString(var Msg: TMessage); message wm_AddString;
            end; <br>

            TMyThread = class(TThread)
            private
            FWnd: hWnd;
            protected
            procedure Execute; override;
            public
            constructor Create(AWnd: hWnd);
            end;<br>

            procedure TForm1.WMAddString(var Msg: TMessage);
            begin
            Memo1.Lines.Add(PChar(Msg.lParam));
            end;<br>

            procedure TMyThread.Execute;
            var
            S: String;
            begin
            while not Terminated do
            begin
            ...
            S := 'test';
            if IsWindow(FWnd) then
            SendMessage(FWnd, wm_AddString, Length(S), Integer(S));
            ...
            end;
            end;<br>

            constructor TMyThread.Create(AWnd: hWnd);
            begin
            inherited Create(False);
            FWnd := AWnd;
            end;<br>

            im form dann<br>

            TMyThread.Create(Handle);

            </pre>

            Gruß Hage

            Comment


            • #7
              Hallo,

              anstelle PostMessage kann man aus dem Thread heraus auch den Zugriff über <b>SendMessageTimeout</b> verwenden. In diesem Fall wird eine TimeOut-Zeitspanne definiert, um im Deadlock-Fall nicht permanent hängen zu bleiben:

              "<i>The SendMessageTimeout function sends the specified message to a window or windows. The function calls the window procedure for the specified window and, if the specified window belongs to a different thread, does not return until the window procedure has processed the message or the specified time-out period has elapsed. If the window receiving the message belongs to the same queue as the current thread, the window procedure is called directly — the time-out value is ignored.</i>&quot

              Comment


              • #8
                Ich fürchte, das potentielle Problem bleibt weiterhin bestehen, <br>
                denn das scheint mir der Aufrof von SendMessage zu sein der ja auch <br>
                in Synchronize erfolgt. Ich stelle mir folgende Situation vor:<br>
                procedure TMyThread.Execute; <br>
                var <br>
                S: String; <br>
                begin <br>
                while not Terminated do <br>
                begin <br>
                ***<br>
                S := 'test'; <br>
                if IsWindow(FWnd) then <br>
                SendMessage(FWnd, wm_AddString, Length(S), Integer(S)); <br>
                ... <br>
                end; <br>
                end; <br>
                <br>
                Wenn nun das Ereignis OnCloseQuery eintritt, wenn sich der <br>
                Thread gerade an der mit *** gekennzeichneten Stelle befindet, <br>
                und im OnCloseQuery-Ereignis folgendes gemacht wird:<br>
                Th.Terminate;<br>
                Th.WaitFor;<br>
                dann muß der Thread noch einmal SendMessage durchlaufen, <br>
                bevor er merkt, daß Terminated true ist. <br>
                Es hilft auch nicht, <br>
                if (not Terminated) then SendMessage(...) <br>
                aufzurufen, denn der Threadwechsel könnte (theoretisch zumindest) <br>
                auch zwischen der if-Abfrage und dem Aufruf von SendMessage erfolgen.<br>
                Die Lösung scheint für mich, wie oben beschrieben, die Entkopplung <br>
                von Th.Terminate; und <br>
                Th.WaitFor; <br>
                zu sein, so daß SendMessage die Gelegenheit bekommt nochmal in die <br>
                Botschaftsschleife des Hauptprogramms einzutreten.<br>
                <br>
                Grüße, Andreas Richte

                Comment


                • #9
                  Hi

                  @Andreas Richter: theoretisch korrekt, es besteht aber ein Unterschied zwischen einem direkten SendMessage() und .Synchronize. .Synchronize() sendet für alle TThread's an ein internes Fenster. Ich bin der Meinung das das von Borland implementierte Verhalten einen DeadLock "provoziert".
                  Das Grundübel ist das Zusammenspiel von .Synchronize und .WaitFor.

                  @Andreas Kosch: ich habe auch schon mit SendMessageTimeout() rumexperimentiert. Mein Resultat ist das wenn SendMessage()zum DeadLock führt dann tut es SendMessageTimeout() auch. Versuche zeigen mir das beide funktionen intern versuchen zum MainThread umzuschalten, aber genau da kommt es zu DeadLock. D.h. auch sendMessageTimeout blockiert. Viel schlimmer noch, damit SendMessageTimeout funktioniert spaltet das Kernel intern ebenfalls einen Thread ab, und dieser blockiert dann ebenfalls.

                  Die Lösung könnte wirklich im .WaitFor liegen.

                  <pre>

                  function TerminateThreadAndWaitFor(Thread: TThread): Integer;
                  var
                  Handle: THandle;
                  Msg: TMsg;
                  begin
                  Handle := Thread.Handle;
                  Thread.Terminate;
                  if GetCurrentThreadID = MainThreadID then
                  begin
                  repeat
                  Application.ProcessMessages; // oder ähnliches Konstruct
                  until WaitForSingleObject(Handle, 50) = WAIT_OBJECT_0;
                  end else
                  WaitForSingleObject(Handle, INFINTE);
                  Result := GetExitCodeThread(Handle);
                  end;

                  </pre>

                  Im Grunde nutzt Delphi eine ähnliche Methodik, der Unterschied ist aber das obiger Code den kompletten MessageQueue abarbeitet. Der Grund ist einfach, da es durchaus vorkommen kann das das System nur blockiert weil bestimmte Messages erst vollständig abgearbeitet werden müssen.

                  Gruß Hage

                  Comment


                  • #10
                    Hallo Hagen,<br>vielen Dank. Aber zwei Fragen habe ich noch.<br>1. Wird DoPaint innerhalb des Threads ausgeführt?<br>2. Warum ergibt
                    FLock.LockCount bei mir immer -1?<br>:-)Jens Schuman

                    Comment


                    • #11
                      1. ja
                      2. hast Du die Critical Section initialisiert ? InitializeCriticalSection(FLock).

                      Bei mir, Win9x, wird .LockCount geändert.

                      Gruß Hage

                      Comment


                      • #12
                        Hallo Hagen,<br>ich nutze Win2k SP1.<br>ich habe FLock als Threadfeld deklariert.<br>Dies ist meine Execute Methode:<br>
                        <pre><font size="1" face="Verdana">
                        procedure TPaintThread.Execute;
                        begin
                        InitializeCriticalSection(FLock);
                        Try
                        While Not Terminated do
                        begin
                        DoPaint;
                        Sleep(0);
                        end;
                        Finally
                        DeleteCriticalSection(FLock);
                        end;
                        end;</font>
                        </pre>
                        <br>:-) Jens Schuman

                        Comment


                        • #13
                          Hi

                          InitializeCriticalSection() sollte nicht im TThread.Execute aufgerufen werden, das ist FALSCH. Also, InitCS() ordned die CS dem aufrufenden Thread zu, so als wäre dieser der Besitzer der CS um Code des Threads vor anderen Threads zu blockieren. D.h. in Deinem obigen Falle wird die CS Deinem Thread zugeordnet und somit .DoPaint() zum Thread zugeordnet. In meinem Code ist FLock eine GLOBALE Variable die auch noch GLOBAL in der Unit Initialisierung erzeugt wird.

                          Gruß Hage

                          Comment


                          • #14
                            Hallo Hagen,<br>ich kann es nicht. Ich glaube, dass ich genau das gemacht, was Du geschrieben hast. Aber nach wie vor ist FLock.Lockcount=-1. Hier ist mein Code:<br>
                            <pre>
                            <font size="1" face="Verdana">unit Unit2;

                            interface

                            uses
                            Classes, Graphics, Windows;

                            type
                            TPaintThread = class(TThread)
                            private
                            { Private-Deklarationen }
                            FColor : TColor;
                            FCanvas : TCanvas;
                            procedure DoPaint;
                            protected
                            procedure Execute; override;
                            public
                            constructor Create(CreateSuspended: Boolean; Canvas : TCanvas; Color : TColor);
                            end;

                            var
                            FLock: TRTLCriticalSection;

                            implementation

                            constructor TPaintThread.Create(CreateSuspended: Boolean; Canvas: TCanvas; Color: TColor);
                            begin
                            FColor:=Color;
                            FCanvas:=Canvas;
                            FreeOnTerminate:=True;
                            Inherited Create(CreateSuspended);
                            end;

                            procedure TPaintThread.DoPaint;
                            var
                            L,T,R,B : Integer;
                            begin
                            If FLock.LockCount=0 then
                            begin
                            EnterCriticalSection(FLock);
                            try
                            FCanvas.Pen.Color:=FColor;
                            FCanvas.Brush.Style:=bsClear;
                            L:=Random(400)+15;
                            T:=Random(400)+15;
                            R:=Random(400)+15;
                            B:=Random(400)+15;
                            FCanvas.Rectangle(L,T,R,B);
                            finally
                            LeaveCriticalSection(FLock);
                            end;
                            end;
                            end;

                            procedure TPaintThread.Execute;
                            begin
                            Try
                            While Not Terminated do
                            begin
                            DoPaint;
                            Sleep(0);
                            end;
                            Finally
                            end;
                            end;

                            initialization
                            InitializeCriticalSection(FLock);
                            finalization
                            DeleteCriticalSection(FLock);
                            end.</font></pre>
                            <br>:-) Jens Schuman

                            Comment


                            • #15
                              Hi Jens

                              Vergiß das mit .LockCount; Es wird im jeweiligen Win System unterschiedlich gehandhabt . Auf'n alten Win95 System wird .LockCount hochgezählt, in Win95b ist es immer NULL und auf NT basierenden Systemen -1, es sei denn es ist eine Systemweite Critical Section.

                              Als Ersatz folgendes:

                              <pre>
                              var
                              FLock: TCriticalSection;
                              FLockCount: Integer = -1;<br>

                              procedure DoPaint;
                              var
                              Locked: Integer;
                              begin
                              Locked := InterlockedIncrement(FLockCount);
                              try
                              if Locked = 0 then
                              begin
                              EnterCriticalSection(FLock);
                              try

                              finally
                              LeaveCriticalSection(FLock);
                              end;
                              end;
                              finally
                              InterlockedDecrement(FLockCount);
                              end;
                              end;

                              </pre>

                              Die Critical Section FLock wird nun eigentlich nicht mehr benötigt und stellt nur noch eine "Sicherheitsmaßnahme" dar.

                              Gruß Hagen, and sorry :

                              Comment

                              Working...
                              X