2017-11-01 18 views
1

내 응용 프로그램이 FormShow 이벤트에서 GET 명령을 사용하면 Back을 누르고 스레드가 시작된 후 닫히고 아직 완료하지 않았기 때문에 Thread Error: Invalid argument (22) 또는 Thread Error: No such process (3)과 같은 오류가 발생할 수 있습니다.Indy HTTP로 스레드를 올바르게 종료하는 방법은 무엇입니까?

procedure TForm58.FormShow(Sender: TObject); 
begin 
    if Assigned(LListThread) then LListThread := nil; 
    LListThread := TLoadListThread.Create; 
    LListThread.OnTerminate := LoadListThreadTerminated; 
    LListThread.Start; 
end; 

constructor TLoadListThread.Create; 
begin 
    inherited Create(True); 
    FreeOnTerminate := True; 
end; 

procedure TLoadListThread.Execute; 
begin 
    if Form58.IdHTTP1.Connected then Form58.IdHTTP1.Disconnect; 
    st := TStringList.Create; 
    try 
     ms := TMemoryStream.Create; 
     Synchronize(
     procedure 
     begin 
      Form58.Label1.Text := 'Loading...'; 
     end); 
     try 
      Form58.IdHTTP1.Get(urlserver,ms); 
      ms.Position := 0; 
      st.LoadFromStream(ms, TEncoding.UTF8); 
     finally 
      ms.Free; 
     end; 
     // Do something with st 
    finally 
     st.Free; 
    end; 
end; 

procedure TForm58.LoadListThreadTerminated(Sender: TObject); 
begin 
    if IdHTTP1.Tag = 1 then 
    begin 
     LListThread := nil; 
     Form58.Close; 
    end else 
    begin 
     LListThread := nil; 
     if TThread(Sender).FatalException = nil then 
     // Do something 
     else 
     // Do otherthing 
    end; 
end; 

procedure TForm58.CloseButtonClick(Sender: TObject); 
begin 
    if Assigned(LListThread) then 
    begin 
    IdHTTP1.Tag := 1; 
    LListThread.Terminate; 
    end else Form58.Close; 
end; 

procedure TForm58.FormClose(Sender: TObject; var Action: TCloseAction); 
begin 
    if Assigned(LListThread) then 
    begin 
    LListThread.Destroy; 
    LListThread := nil; 
    end; 
    try 
    // Do something 
    finally 
    Action := TCloseAction.caFree; 
    end; 
end; 

내가 작성한이 스레드 시작/종료 논리는 매우 실망 스럽다. 어떻게 개선하고 오류 메시지가 표시되지 않도록 할 수 있습니까?

+1

다른 전략을 사용해보십시오. get이 끝날 때까지 양식을 닫지 마십시오. – nolaspeaker

+1

스레드 세이프 전략을 사용하십시오 : Execute 메서드 내에서만 TIdHTTP를 생성하고 사용하십시오 – mjn42

+1

다음 연습을 심각하게 고려해야합니다 : 폼 단위를 사용하지 않는 * 자체 * 단위로 스레드를 작성하거나 그 문제에 대한 VCL 단위. VCL은 스레드로부터 안전하지 않습니다. 스레드 내에서 양식에 직접 액세스하는 것은 허위입니다. –

답변

4

폼을 닫을 준비가되면 스레드가 계속 실행 중이면 종료하도록 신호를 보내지 만 완전히 종료 될 때까지 기다리지 않고 양식을 실제로 삭제할 때 스레드 개체를 명시 적으로 삭제합니다 FreeOnTerminate=True을 사용해도 닫힙니다.

명시 적으로 스레드 개체를 삭제하기 전에 TThread.WaitFor()으로 전화해야하지만 표시되는 오류의 원인이되는 FreeOnTerminate=True을 사용할 때는 작동하지 않습니다. 그 외에도 스레드가 여전히 실행 중이며 명시 적으로 스레드 객체를 파괴하고있는 경우 TThread 소멸자가 WaitFor()을 호출합니다. 그래서 어느 쪽이든, 당신은 오류를 일으키고 있습니다.

그래서, 당신이 필요로 다음 중 하나

  • FreeOnTerminate=False을 설정 한 다음 완전히 명시 적으로 파괴하기 전에 종료 스레드를 기다립니다.

  • 집합 FreeOnTerminate=True 그리고 스레드 개체를 전혀 수동으로 파괴하지 말고 스레드가 종료 될 때까지 양식을 닫지 마십시오.

은 또한 다음과 같은 TIdHTTP.OnWork 사건에서와 같이 GET 요청을 중단 스레드 내에서 확인할 수 있습니다 플래그를 설정하는 스레드의 가상 TerminatedSet() 메서드를 재정 건의 할 것입니다.

해보십시오이, 같은 FreeOnTerminated=True를 사용하여 :

type 
    TLoadListThread = class(TThread) 
    private 
    FUrl: string; 
    FOnLoading: TNotifyEvent; 
    DoAbort: Boolean; 
    procedure CheckAbort; 
    procedure DoLoading; 
    procedure HTTPWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64); 
    protected 
    procedure TerminatedSet; override; 
    public 
    constructor Create(const AUrl: String); 
    property OnLoading: TNotifyEvent read FOnLoading write FOnLoading; 
    end; 

constructor TLoadListThread.Create(const AUrl: String); 
begin 
    inherited Create(True); 
    FreeOnTerminate := True; 
    FUrl := AUrl; 
end; 

procedure TLoadListThread.CheckAbort; 
begin 
    if DoAbort then SysUtils.Abort; 
end; 

procedure TLoadListThread.DoLoading; 
begin 
    if Assigned(FOnLoading) then FOnLoading(Self); 
end; 

procedure TLoadListThread.HTTPWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64); 
begin 
    CheckAbort; 
end; 

procedure TLoadListThread.TerminatedSet; 
begin 
    inherited; 
    DoAbort := True; 
end; 

procedure TLoadListThread.Execute; 
var 
    HTTP: TIdHTTP; 
begin 
    HTTP := TIdHTTP.Create; 
    try 
    st := TStringList.Create; 
    try 
     ms := TMemoryStream.Create; 
     try 
     if Assigned(FOnLoading) then Synchronize(DoLoading); 
     CheckAbort; 
     HTTP.Get(FUrl, ms); 
     ms.Position := 0; 
     st.LoadFromStream(ms, TEncoding.UTF8); 
     finally 
     ms.Free; 
     end; 
     CheckAbort; 
     // Do something with st 
    finally 
     st.Free; 
    end; 
    finally 
    HTTP.Free; 
    end; 
end; 

private 
    procedure CloseOnTerminated(Sender: TObject); 

procedure TForm58.FormShow(Sender: TObject); 
begin 
    StopLoadListThread; 
    LListThread := TLoadListThread.Create(urlserver); 
    LListThread.OnLoading := LoadListThreadLoading; 
    LListThread.OnTerminate := LoadListThreadFinished; 
    LListThread.Start; 
end; 

procedure TForm58.StopLoadListThread; 
begin 
    if Assigned(LListThread) then 
    begin 
    LListThread.OnLoading := nil; 
    LListThread.OnTerminate := nil; 
    LListThread.Terminate; 
    LListThread := nil; 
    end; 
end; 

procedure TForm58.LoadListThreadLoading(Sender: TObject); 
begin 
    Label1.Text := 'Loading...'; 
end; 

procedure TForm58.LoadListThreadFinished(Sender: TObject); 
begin 
    if LListThread.FatalException = nil then 
    // Do something 
    else 
    // Do something else 

    LListThread := nil; 
end; 

procedure TForm58.CloseOnTerminated(Sender: TObject); 
begin 
    LListThread := nil; 
    Close; 
end; 

procedure TForm58.CloseButtonClick(Sender: TObject); 
begin 
    Close; 
end; 

procedure TForm58.FormClose(Sender: TObject; var Action: TCloseAction); 
begin 
    if Assigned(LListThread) then 
    begin 
    LListThread.OnTerminate := CloseOnTerminated; 
    LListThread.Terminate; 
    Action := TCloseAction.caNone; 
    end 
    else 
    begin 
    // Do something 
    Action := TCloseAction.caFree; 
    end; 
end; 

또는이, FreeOnTerminated=False를 사용하여 :

type 
    TLoadListThread = class(TThread) 
    private 
    FUrl: string; 
    FOnLoading: TNotifyEvent; 
    DoAbort: Boolean; 
    procedure CheckAbort; 
    procedure DoLoading; 
    procedure HTTPWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64); 
    protected 
    procedure TerminatedSet; override; 
    public 
    constructor Create(const AUrl: String); 
    property OnLoading: TNotifyEvent read FOnLoading write FOnLoading; 
    end; 

constructor TLoadListThread.Create(const AUrl: String); 
begin 
    inherited Create(True); 
    FreeOnTerminate := False; 
    FUrl := AUrl; 
end; 

procedure TLoadListThread.CheckAbort; 
begin 
    if DoAbort then SysUtils.Abort; 
end; 

procedure TLoadListThread.DoLoading; 
begin 
    if Assigned(FOnLoading) then FOnLoading(Self); 
end; 

procedure TLoadListThread.HTTPWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64); 
begin 
    CheckAbort; 
end; 

procedure TLoadListThread.TerminatedSet; 
begin 
    inherited; 
    DoAbort := True; 
end; 

procedure TLoadListThread.Execute; 
var 
    HTTP: TIdHTTP; 
begin 
    HTTP := TIdHTTP.Create; 
    try 
    st := TStringList.Create; 
    try 
     ms := TMemoryStream.Create; 
     try 
     if Assigned(FOnLoading) then Synchronize(DoLoading); 
     CheckAbort; 
     HTTP.Get(FUrl, ms); 
     ms.Position := 0; 
     st.LoadFromStream(ms, TEncoding.UTF8); 
     finally 
     ms.Free; 
     end; 
     CheckAbort; 
     // Do something with st 
    finally 
     st.Free; 
    end; 
    finally 
    HTTP.Free; 
    end; 
end; 

procedure TForm58.FormShow(Sender: TObject); 
begin 
    StopLoadListThread; 
    LListThread := TLoadListThread.Create(urlserver); 
    LListThread.OnLoading := LoadListThreadLoading; 
    LListThread.OnTerminate := LoadListThreadFinished; 
    LListThread.Start; 
end; 

procedure TForm58.StopLoadListThread; 
begin 
    if Assigned(LListThread) then 
    begin 
    LListThread.OnLoading := nil; 
    LListThread.OnTerminate := nil; 
    LListThread.Terminate; 
    LListThread.WaitFor; 
    FreeAndNil(LListThread); 
    end; 
end; 

procedure TForm58.LoadListThreadLoading(Sender: TObject); 
begin 
    Label1.Text := 'Loading...'; 
end; 

procedure TForm58.LoadListThreadFinished(Sender: TObject); 
var 
    Thread: TThread; 
begin 
    Thread := TThread(Sender); 

    if Thread.FatalException = nil then 
    // Do something 
    else 
    // Do something else 

    // if using 10.1 Berlin or earlier: 
    TThread.CreateAnonymousThread(
    procedure 
    begin 
     TThread.Queue(nil, 
     procedure 
     begin 
      Thread.Free; 
     end 
    ); 
    end; 
).Start; 

    // if using 10.2 Tokyo or later: 
    TThread.ForceQueue(nil, 
    procedure 
    begin 
     Thread.Free; 
    end 
); 
end; 

procedure TForm58.CloseButtonClick(Sender: TObject); 
begin 
    Close; 
end; 

procedure TForm58.FormClose(Sender: TObject; var Action: TCloseAction); 
begin 
    StopLoadListThread; 
    // Do something 
    Action := TCloseAction.caFree; 
end;