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.
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
.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With