2017-12-13 7 views
0

Ich warte auf den Thread zu beenden, aber ohne Erfolg, es bleibt in der WaitFor() -Methode stecken; und kommt nicht zurück und steht dort auf unbestimmte Zeit.delphi thread waitfor unendlich (nie terminieren)

procedure TForm1.btnStopClick(Sender: TObject); 

Kann mir jemand helfen?

Ich bin mit Delphi Berlin 10.1 Update 2 auf einem Windows 10-Version 64-Bit läuft 1709 16299,64

folgt dem Code:

unit untPrincipal; 

interface 

uses 
    Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, 
    Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls; 

const 
    WM_TEST_SERVICE = WM_APP + 1; 

type 
    TForm1 = class(TForm) 
     btnStart: TButton; 
     mmoOutput: TMemo; 
     btnStop: TButton; 
     procedure btnStartClick(Sender: TObject); 
     procedure btnStopClick(Sender: TObject); 
    private 
     { Private declarations } 
     threadService: TThread; 
     procedure OnThreadTerminate(Sender: TObject); 
     procedure WMTestService(var msg: TMessage); message WM_TEST_SERVICE; 
    public 
     { Public declarations } 
    end; 

    IThreadInfo = interface 
     ['{B179712B-8B14-4D54-86DA-AB22227DBCAA}'] 
     function IsRunning: Boolean; 
    end; 

    IService = interface 
     ['{30934A11-1FB9-46CB-8403-F66317B50199}'] 
     procedure ServiceCreate(); 
     procedure ServiceStart(const info: IThreadInfo); 
    end; 

    TMyService = class(TInterfacedObject, IService) 
    private 
     handle: THandle; 
    public 
     constructor Create(const handle: THandle); 
     procedure ServiceCreate; 
     procedure ServiceStart(const info: IThreadInfo); 
    end; 

    TThreadService = class(TThread) 
    private 
     service: IService; 
    protected 
     procedure Execute; override; 
    public 
     constructor Create(const service: IService); 
    end; 

    TThreadInfo = class(TInterfacedObject, IThreadInfo) 
    private 
     thread: TThread; 
    public 
     constructor Create(const thread: TThread); 
     function IsRunning: Boolean; 
    end; 

    TThreadPost = class(TThread) 
    private 
     handle: THandle; 
     info: IThreadInfo; 
    protected 
     procedure Execute; override; 
    public 
     constructor Create(const handle: THandle; const info: IThreadInfo); 
    end; 

var 
    Form1: TForm1; 

implementation 

{$R *.dfm} 

{ TForm1 } 

procedure TForm1.btnStartClick(Sender: TObject); 
var 
    service: IService; 
begin 
    service := TMyService.Create(Self.handle); 
    threadService := TThreadService.Create(service); 
    threadService.OnTerminate := OnThreadTerminate; 
    threadService.Start; 
end; 

procedure TForm1.btnStopClick(Sender: TObject); 
begin 
    if Assigned(threadService) then 
    begin 
     try 
     threadService.Terminate; 
     threadService.WaitFor; 
     finally 
     if Assigned(threadService) then 
      FreeAndNil(threadService); 
     end; 
    end; 
end; 

procedure TForm1.OnThreadTerminate(Sender: TObject); 
begin 
    mmoOutput.Lines.Add(DateTimeToStr(Now()) + ' - procedure TForm1.OnThreadTerminate(Sender: TObject);'); 
end; 

procedure TForm1.WMTestService(var msg: TMessage); 
begin 
    mmoOutput.Lines.Add(DateTimeToStr(Now()) + ' - Service'); 
end; 

{ TMyService } 

constructor TMyService.Create(const handle: THandle); 
begin 
    inherited Create(); 
    Self.handle := handle; 
end; 

procedure TMyService.ServiceCreate; 
begin 
    PostMessage(handle, WM_TEST_SERVICE, 0, 0); 
end; 

procedure TMyService.ServiceStart(const info: IThreadInfo); 
var 
    thread: TThreadPost; 
begin 
    while info.IsRunning do 
    begin 
     thread := TThreadPost.Create(handle, info); 
     try 
     thread.Start; 
     thread.WaitFor; 
     ShowMessage('Never Execute'); 
     finally 
     thread.Free; 
     end; 
    end; 
end; 

{ TThreadService } 

constructor TThreadService.Create(const service: IService); 
begin 
    inherited Create(True); 
    Self.service := service; 
end; 

procedure TThreadService.Execute; 
begin 
    service.ServiceCreate; 
    service.ServiceStart(TThreadInfo.Create(Self) as IThreadInfo); 
end; 

{ TThreadInfo } 

constructor TThreadInfo.Create(const thread: TThread); 
begin 
    inherited Create(); 
    Self.thread := thread; 
end; 

function TThreadInfo.IsRunning: Boolean; 
begin 
    Result := not thread.CheckTerminated; 
end; 

{ TThreadPost } 

constructor TThreadPost.Create(const handle: THandle; const info: IThreadInfo); 
begin 
    inherited Create(True); 
    Self.handle := handle; 
    Self.info := info; 
end; 

procedure TThreadPost.Execute; 
begin 
    while info.IsRunning do 
    begin 
     PostMessage(handle, WM_TEST_SERVICE, 0, 0); 
     Sleep(1000); 
    end; 
end; 

end. 
+2

Warum würden Sie das kurz nach dem Start des Threads aufrufen? Warum haben Sie überhaupt einen Thread? Warum überprüft der Thread nie auf "Beendet"? –

+0

Dieser Code soll nur ein Problem zeigen, das ich in einem größeren System habe, wo mehrere IService läuft und jeder seinen eigenen Thread hat, wenn ich das System finalisiere (Windows Service für den Fall) muss ich alle Threads beenden, Warten Sie, bis die gleichen fertig sind und andere Operationen ausgeführt werden. Das Problem ist, wenn ich fertig bin.jeder IService ist anders implementiert und in einigen Fällen laufen mehr Threads in ihnen, und dort habe ich das Problem gefunden – Passella

+0

@ JerryDodge, Du hast gesagt, dass die WaitFor() Methode unnötig ist, aber wie warte ich auf das Ende von Thread dieser Punkt? danke – Passella

Antwort

2

Sie rufen:

function TThreadInfo.IsRunning: Boolean; 
begin 
    Result := not thread.CheckTerminated; 
end; 

von TThreadPost.Execute, die versucht zu überprüfen, ob thread Instanz beendet ist oder nicht.

Das Problem besteht darin, dass der Anruf an CheckTerminated den aktuellen Thread beendet Status verwendet, nicht die thread Instanz. (Denken Sie darüber nach, was passiert wäre, wenn die thread Instanz beendet und freigegeben wurde, als thread.CheckTerminated aufgerufen wurde, falls das möglich war).

Das Ergebnis ist, dass IsRunning wird nie falsch sein, und Sie werden eine Endlosschleife haben. Sie müssen neu entwerfen, wie Sie die Threads auf sichere Weise stoppen können.

+0

Ich wollte meine Antwort posten, als ich Ihre sah. Ich brauchte 10 Minuten, um zu entscheiden, ob ich posten sollte oder nicht. –

+0

@NasreddineAbdelillahGalfout, es ist in Ordnung. Im Moment kann ich keine gute Lösung empfehlen, abgesehen von einem Redesign. Es ist keine gute Lösung, Threads und komplexe Logik zusammen zu verschachteln. Ich versuche immer, Verantwortlichkeiten in einem Erzeuger/Verbraucher-Schema zu trennen, und Logik/Daten sollten nur weitergegeben, nicht geteilt werden. –

+0

Ja, ein Redesign wäre der Weg dorthin. Ich denke, meine Antwort deckt ab, warum die Sackgasse passiert ist. Ich werde dir die Lösung überlassen. :) –

1

bevor wir irgendwas anfangen, bitte nächstes mal die Namen der Felder in deiner Klasse mit einem 'F' beginnen.

Lassen Sie sich Ihren Code Schritt durchläuft Schritt mit der ersten Benutzeraktion starten

procedure TForm1.btnStartClick(Sender: TObject); 
  1. service := TMyService.Create(Self.handle);

Sie eine Instanz von TMyService erstellen und zuweisen TForm.Handle auf das Feld handle (die Sie sollte FHandle benennen).

  1. threadService := TThreadService.Create(service);

Sie eine Bewährungs Instanz TThread erstellen und zuweisen service zu seinem privaten Bereich service (wieder sollten Sie es FService nennen und Sie brauchen nicht selbst zu verwenden,)

eine Sache ist, dass die Referenz dieses Mal im Gegensatz zu der ersten Zeile beibehalten wird, wo die Referenz am Ende des Bereichs stirbt/verloren geht.

  1. threadService.OnTerminate := OnThreadTerminate;

OnTerminate Ereignishandler zuweisen.

Onterminate verwendet internal synchronize(), dies könnte die Ursache für den Deadlock sein.(< --- Zukunft Fehler anfällig eins)

  1. threadService.Start;

Sie das suspendierte threadService starten.

An dieser Stelle haben Sie jetzt zwei Threads, die MainThread und threadService ausführen (MyService wird im Kontext von MainThread ausgeführt).

der MainThread ist Idle wartet auf mehr Benutzeraktion oder Umgang mit anderen Nachrichten (z. B. neu streichen, Größe ändern, Formular verschieben .... etc).

threadService führt seine Ausführungsmethode aus.

jetzt lassen Sie uns folgen, was die TThreadService.Execute; ist dabei

  1. service.ServiceCreate;

hier Sie eine Nachricht an handle == TForm.Handle (< --future Fehler anfällig zwei) schreiben

    service.ServiceStart(TThreadInfo.Create(Self) as IThreadInfo);

    2,1 while info.IsRunning do

Ihr Problem ist hier, weil info.IsRunning prüft, ob die Flagge in dem aktuellen Thread beenden, die threadService (intern sonst eine Ausnahme ausgelöst wird) ist (< - Zukunft fehleranfällig drei).

2.2 **the catastrophe code** 

    begin 
     thread := TThreadPost.Create(handle, info); 
     try 
     thread.Start; 
     thread.WaitFor; 
     ShowMessage('Never Execute'); 
     finally 
     thread.Free; 
     end; 
    end; 

hier erstellen Sie TThreadPost was ein anderer Thread ist und starten. dann rufst du waitfor auf es TThreadService Verriegelung. jetzt haben Sie drei Threads ausgeführt: MainThread (Idle), threadService (Deadlocked) und TThreadPost (lose).

Im TThreadPost execute-Methode gibt es eine weitere while info.IsRunning do

für die Flagge beenden prüft, aber es ist die TThreadPost ist man nicht die threadService ein.

Wenn also der Benutzer auf die Schaltfläche Stop klickt, wartet der Aufruf waitfor im MainThread auf einen festgefahrenen Thread.

als eine Lösung, die Sie tun, wie LU RD sagte (Ich schrieb meine Antwort, als er seine veröffentlichte).