不赞成使用符号'Resume'/线程错误:句柄无效(6)

WeG*_*ars 9 delphi delphi-xe

我有一段旧代码,我想将它升级到Delphi XE.我有一个编译器警告Resume,我想替换它,Start但程序崩溃.

constructor THTTPGetThread.Create(aAcceptTypes, aAgent, aURL, aFileName, aUserName, aPassword, aPostQuery, aReferer: String; aBinaryData, aUseCache: Boolean; aProgress: TOnProgressEvent; aToFile: Boolean);
begin
  FreeOnTerminate := True;
  inherited Create(True);

  FTAcceptTypes := aAcceptTypes;
  FTAgent       := aAgent;
  FTURL         := aURL;
  FTFileName    := aFileName;
  FTUserName    := aUserName;
  FTPassword    := aPassword;
  FTPostQuery   := aPostQuery;
  FTReferer     := aReferer;
  FTProgress    := aProgress;
  FTBinaryData  := aBinaryData;
  FTUseCache    := aUseCache;
  FTToFile      := aToFile;

  Resume;      <------------ works, but I get compiler warning
  //Start;     <------------ doesn't work
end;
Run Code Online (Sandbox Code Playgroud)

我使用START时得到的错误是:"线程错误:句柄无效(6)".
我不想要复杂的东西(冻结/同步线程).我只是想从互联网上下载文件而不阻止GUI.

Dav*_*nan 15

简单的答案是,您不应该创建此线程,因为您希望它立即启动.删除对调用Start并传递False给继承的构造函数.

请注意,在所有构造函数都运行完成之前,线程不会启动,因此其含义与您发布的代码相同.


至于您的代码失败的原因,请查看以下摘自源代码:

procedure TThread.AfterConstruction;
begin
  if not FCreateSuspended and not FExternalThread then
    InternalStart(True);
end;

procedure TThread.InternalStart(Force: Boolean);
begin
  if (FCreateSuspended or Force) and not FFinished and not FExternalThread then
  begin
    FSuspended := False;
    FCreateSuspended := False;
    if ResumeThread(FHandle) <> 1 then
      raise EThread.Create(SThreadStartError);
  end
  else
    raise EThread.Create(SThreadStartError);
end;

procedure TThread.Start;
begin
  InternalStart(False);
end;
Run Code Online (Sandbox Code Playgroud)

您的代码使用调用继承的构造函数CreateSuspended=True.这将FCreateSuspended成为True.然后StartTThread.AfterConstruction运行之前调用.这成功启动了线程,但是,重要的是它重置FCreateSuspendedFalse.然后当TThread.AfterConstruction它尝试恢复失败的线程,因为它已经在运行.

我认为Delphi代码很好,因为Start从构造函数调用它是不正确的.您需要确保在调用之后所有构造函数都运行并派生类构造函数Start.您还没有任何派生类,但这不是重点 - 重点Start是不支持从构造函数调用.

最重要的是你应该创建这个未被暂停的线程Start,并代表你进行调用AfterConstruction.

  • 不能在构造函数中调用`Start()`.在构造函数退出之后,如果仅设置`CreateSuspended = True`,则打算调用*.如果设置`CreateSuspended = False`,则线程会自动启动,因此无需手动调用`Start()`. (2认同)

Ken*_*ssa 7

问题来自用于确保线程在所有构造函数完成之前不会启动的机制(如David所述).

你看,无论你作为参数传递给构造函数,所有线程实际上都是以挂起状态创建的.这样做是为了确保线程在构造函数完成其工作之前不会实际启动.一旦在AfterConstruction函数中完成所有构造函数(如果CreateSuspended为false),则启动线程.以下是您的问题的相关代码部分:

procedure TThread.AfterConstruction;
begin
  if not FCreateSuspended and not FExternalThread then
    InternalStart(True);
end;

procedure TThread.Start;
begin
  InternalStart(False);
end;

procedure TThread.InternalStart(Force: Boolean);
begin
  if (FCreateSuspended or Force) and not FFinished and not FExternalThread then
  begin
    FSuspended := False;
    FCreateSuspended := False;
    if ResumeThread(FHandle) <> 1 then
      raise EThread.Create(SThreadStartError);
  end
  else
    raise EThread.Create(SThreadStartError);
end;
Run Code Online (Sandbox Code Playgroud)

在您的情况下发生的情况是,当您致电Start时,它可以正常工作.它调用InternalStart,清除FSuspended和FCreateSuspended标志,然后恢复线程.之后,一旦构造函数完成,就执行AfterConstruction.AfterConstruction检查FCreateSuspended(已在调用Start时清除)并尝试启动线程,但线程已在运行.

为什么叫简历有效?继续不清除FCreateSuspended标志.

所以,总结一下,如果你想让你的线程在创建后自动启动,只需将False传递给TThread的构造函数的CreateSuspended参数,它就会像魅力一样工作.

至于Resume/Suspend被弃用的原因......我最好的猜测是,首先暂停线程(在创建时除外)是不好的做法,因为线程暂停时无法保证线程的状态.除其他外,它可以锁定资源.如果所述线程有一个消息队列,它将停止回答它们,这会导致依赖于广播消息的进程出现问题,例如使用ShellExecute打开URL(至少在Internet Explorer中,不确定其他浏览器是否受到影响).因此,他们弃用了Resume/Suspend并添加了一个新函数来"恢复"一个被创建暂停的线程(即Start).