Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symbol 'Resume' is deprecated/Thread Error: The handle is invalid (6)

I have an old piece of code that I want to upgrade it to Delphi XE. I have a compiler warning about Resume and I want to replace it with Start but the program crashes.

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;

The error I get when I use START is: "Thread Error: The handle is invalid (6)".
I don't want complex stuff (freeze/synchronize threads). I just want to download a file from internet without blocking the GUI.

like image 614
Server Overflow Avatar asked Jul 20 '11 13:07

Server Overflow


2 Answers

The simple answer is that you should not create this thread suspended since you want it to start immediately. Remove the call to Start and pass False to the inherited constructor.

Note that the thread isn't started until all constructors have run to completion so the meaning is identical to your posted code.


As to why your code fails, look at the following excerpts from the source:

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;

Your code calls the inherited constructor with CreateSuspended=True. This sets FCreateSuspended to be True. You then call Start before TThread.AfterConstruction runs. This succeeds in starting the thread, but, crucially it resets FCreateSuspended to False. Then when TThread.AfterConstruction it tries to resume the thread which fails because it is already running.

I think the Delphi code is fine because it is incorrect to call Start from the constructor. You need to be sure that all constructors have run and derived class constructors run after your call to Start. You don't have any derived classes yet but that's not the point—the point is that calling Start from a constructor is not supported.

The bottom line is that you should create this thread not suspended and let Start be called, on your behalf, from AfterConstruction.

like image 145
David Heffernan Avatar answered Jan 25 '23 06:01

David Heffernan


The problem comes from the mechanic used to ensure the thread won't start until all constructors are done (Like mentioned by David).

You see, all thread are actually created in suspended state no matter what you pass as argument to the constructor. This is done to ensure the thread don't actually starts before the constructor has finished its work. Starting the thread is done once all the constructor are done in the AfterConstruction function (If CreateSuspended is false). Here's the pertinent part of code to your problem:

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;

What happens in your situation is, when you call Start, it works fine. It calls InternalStart which clear the FSuspended and FCreateSuspended flag and then resume the thread. After that, once the constructor is done, AfterConstruction is executed. AfterConstruction checks for FCreateSuspended (Which has already been cleared in the call to Start) and try to start the thread, but the thread is already running.

Why calling resume works? Resume don't clear the FCreateSuspended flag.

So, to sums it, if you want your thread to start automatically once it's created, just pass False to the CreateSuspended parameter of TThread's constructor and it will work like a charm.

As for the reason Resume/Suspend are deprecated... My best guess is that it was bad practice to suspend thread in the first place (beside at creation) because the was no guarantee on the state of the thread when it's suspended. It could be locking a resource, among other things. If said thread has a message queue, it would stop answering them which cause problems for process relying on broadcast message, like using ShellExecute to open a URL (At least with Internet Explorer, not sure if other browsers are affected). So, they deprecated Resume/Suspend and added a new function to "Resume" a thread that was created suspended (i.e. Start).

like image 35
Ken Bourassa Avatar answered Jan 25 '23 06:01

Ken Bourassa