Android 如何使用Indy HTTP Get命令正确终止线程?

Android 如何使用Indy HTTP Get命令正确终止线程?,android,multithreading,delphi,indy,indy10,Android,Multithreading,Delphi,Indy,Indy10,我的应用程序在FormShow事件上执行GET命令,有时用户可能会在线程启动且尚未完成后按Back并关闭表单,从而导致错误,如线程错误:无效参数(22)或线程错误:无此类进程(3) 我知道我创建的这个线程开始/结束逻辑很糟糕,我如何改进它并从显示中删除错误消息?当准备关闭表单时,如果线程仍在运行,那么您将发出终止信号,但您不会等待它完全终止,然后在窗体实际关闭时显式销毁线程对象,即使使用的是FreeOnTerminate=True 在显式销毁线程对象之前,应该调用TThread.WaitFor(

我的应用程序在
FormShow
事件上执行
GET
命令,有时用户可能会在线程启动且尚未完成后按
Back
并关闭表单,从而导致错误,如
线程错误:无效参数(22)
线程错误:无此类进程(3)


我知道我创建的这个线程开始/结束逻辑很糟糕,我如何改进它并从显示中删除错误消息?

当准备关闭表单时,如果线程仍在运行,那么您将发出终止信号,但您不会等待它完全终止,然后在窗体实际关闭时显式销毁线程对象,即使使用的是
FreeOnTerminate=True

在显式销毁线程对象之前,应该调用
TThread.WaitFor()
,但在使用
FreeOnTerminate=True
时,这不起作用,这会导致您看到的错误类型。除此之外,如果线程仍在运行,并且您正在显式销毁线程对象,则
TThread
析构函数会对自身调用
WaitFor()
。因此,无论哪种方式,都会导致错误

因此,您需要:

  • 设置
    FreeOnTerminate=False
    ,然后等待线程完全终止,然后再显式销毁它

  • 设置
    FreeOnTerminate=True
    并且根本不手动销毁线程对象,并且在线程终止之前不关闭窗体

我还建议重写线程的virtual
TerminatedSet()
方法来设置一个标志,然后可以在线程内部检查该标志以中止
GET
请求,例如在
TIdHTTP.OnWork
事件中

使用
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;

或者,当使用
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;


当准备关闭窗体时,如果线程仍在运行,则发出终止的信号,但不等待它完全终止,然后在窗体实际关闭时显式销毁线程对象,即使使用的是
FreeOnTerminate=True

在显式销毁线程对象之前,应该调用
TThread.WaitFor()
,但在使用
FreeOnTerminate=True
时,这不起作用,这会导致您看到的错误类型。除此之外,如果线程仍在运行,并且您正在显式销毁线程对象,则
TThread
析构函数会对自身调用
WaitFor()
。因此,无论哪种方式,都会导致错误

因此,您需要:

  • 设置
    FreeOnTerminate=False
    ,然后等待线程完全终止,然后再显式销毁它

  • 设置
    FreeOnTerminate=True
    并且根本不手动销毁线程对象,并且在线程终止之前不关闭窗体

我还建议重写线程的virtual
TerminatedSet()
方法来设置一个标志,然后可以在线程内部检查该标志以中止
GET
请求,例如在
TIdHTTP.OnWork
事件中

使用
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;

或者,当使用
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;


尝试一种不同的策略:不允许窗体在完成之前关闭。使用线程安全策略:仅在执行方法中创建和使用TIDHTTP。您应该认真考虑以下实践:将线程写入自己的单元中,不使用任何表单单元,或者任何VCL单元。VCL不是线程安全的。直接从一个线程中访问表单是不符合逻辑的。它是不符合逻辑的。尝试不同的策略:不允许窗体在完成之前关闭。使用线程安全策略:仅在执行方法中创建和使用TIDHTTP;您应该认真考虑以下实践:以自己的单位编写线程。不使用任何形式单位,或任何VCL单位。VCL不是线程安全的。从一个线程中直接访问表单是非常糟糕的,这是不符合逻辑的。
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;