【问题标题】:Symbol 'Resume' is deprecated/Thread Error: The handle is invalid (6)不推荐使用符号“恢复”/线程错误:句柄无效 (6)
【发布时间】:2011-10-09 09:36:40
【问题描述】:

我有一段旧代码,我想将它升级到 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;

我使用 START 时得到的错误是:“线程错误:句柄无效 (6)”。
我不想要复杂的东西(冻结/同步线程)。我只想在不阻塞 GUI 的情况下从 Internet 下载文件。

【问题讨论】:

  • TThread 是 THTTPGetThread 的直系祖先吗?

标签: delphi delphi-xe


【解决方案1】:

简单的答案是您不应该创建该线程挂起,因为您希望它立即启动。移除对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;

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

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

最重要的是,您应该创建该线程而不是暂停,并让 Start 代表您从 AfterConstruction 调用。

【讨论】:

  • 嗨,大卫。这正是我为“快速修复”我的代码所做的。但我仍然想了解为什么反过来不起作用。我的意思是,代码很简单。它应该工作。我找到的文档说 Start 应该替换 Resume。但事实并非如此。
  • 我猜开始需要在构造函数之后,但我需要阅读源代码才能知道原因。
  • Start() 不得在构造函数中调用。它旨在在构造函数退出后调用,仅在设置CreateSuspended=True 的情况下。如果设置CreateSuspended=False,线程会为你自动启动,所以不需要手动调用Start()
  • 好的。因此,Start 不会直接替换 Resume。谢谢。
【解决方案2】:

问题来自用于确保线程在所有构造函数都完成之前不会启动的机制(就像 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;

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

为什么调用简历有效?恢复不要清除 FCreateSuspended 标志。

因此,总而言之,如果您希望线程在创建后自动启动,只需将 False 传递给 TThread 构造函数的 CreateSuspended 参数,它就会像魅力一样工作。

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

【讨论】:

  • +1 有趣的是,我的编辑和你的回答几乎相同并且同时出现!
  • 嗯,一个简单问题的 2 个准确答案必须是相似的!那么……我们是在同一分钟内发布的?
猜你喜欢
  • 1970-01-01
  • 2013-08-01
  • 1970-01-01
  • 2019-10-16
  • 1970-01-01
  • 1970-01-01
  • 2021-11-24
  • 1970-01-01
相关资源
最近更新 更多