Reputation: 15
I have a Query
and open it in my Thread
. It works correctly and I don't want to use Synchronize
, because Synchronize
makes main Form
don't response while the Query
not complete fetch.
When close the Form
blow error shown:
System Error. Code: 1400. Invalid window handle
type
TMyThread = class(TThread)
public
procedure Execute; override;
procedure doProc;
end; { type }
.
.
.
procedure TMyThread.doProc;
begin
Form1.Query1.Open;
end;
procedure TMyThread.Execute;
begin
inherited;
doProc;
end;
.
.
.
procedure TForm1.Button1Click(Sender: TObject);
begin
thrd := TMyThread.Create(True);
thrd.FreeOnTerminate := True;
thrd.Resume;
end;
Note : Query
has a lot of record.
Upvotes: 1
Views: 3110
Reputation: 76641
The problem is that the VCL is not thread safe.
In order to have the query execute in parallel to all other things going on you'll have to decouple it from the Form.
That means you'll have to create the Query at runtime using code:
type
TMyThread = class(TThread)
private
FQuery: TQuery;
FOnTerminate: TNotifyEvent;
public
constructor Create(AQuery: TQuery);
destructor Destroy; override;
procedure Execute; override;
procedure doProc;
//Add an event handler to do cleanup on termination.
property OnTerminate: TNotifyEvent read FOnTerminate write FOnTerminate;
end; { type }
constructor TMyThread.Create(AQuery: TQuery);
begin
inherited Create(True);
FQuery:= AQuery;
end;
procedure TMyThread.doProc;
begin
FQuery1.Open;
Synchronize(
//anonymous method, use a separate procedure in older Delphi versions
procedure
begin
Form1.Button1.Enabled:= true; //reenable the button when we're done.
end
);
end;
procedure TMyThread.Execute;
begin
inherited;
doProc;
end;
destructor TMyThread.Destroy;
begin
if Assigned(FOnterminate) then FOnTerminate(Self);
inherited;
end;
In the OnClick
for Button1 you'll do the following:
type
TForm1 = class(TForm)
private
AQuery: TQuery;
...
end; {type}
procedure TForm1.Button1Click(Sender: TObject);
begin
Button1.Enabled:= false; //disable the button so it cannot be started twice.
thrd.Free;
AQuery:= TQuery.Create;
AQuery.SQL.Text:= .....
thrd := TMyThread.Create(AQuery);
thrd.OnTerminate:= MyTerminationHandler;
thrd.FreeOnTerminate:= False;
thrd.Resume;
end;
Finally assign cleanup code to the termination handler of the thread.
If you destroy the Query in the thread then you cannot use FreeOnTerminate:= true
, but you'll have to Free the thread yourself.
procedure TForm1.MyTerminationHandler(Sender: TObject);
begin
FreeAndNil(AQuery);
end;
Warning
This code will only work if you start 1 thread.
If you want start this thread multiple times (i.e. run multiple queries at the same time), you'll have to create an array of threads e.g.:
TQueryThreads = record
MyThread: TMyThread;
MyQuery: TQuery;
constructor Create(SQL: string);
end; {record}
TForm1 = class(TForm)
private
Threads: array of TQueryThreads;
....
end; {TForm1}
Note that this code will not work in the BDE, because that library does not support multiple running queries at the same time
If you want to do that you'll have to use ZEOS or something like that.
As per TLama's suggestion:
I would suggest switching the BDE TQuery component to ADO, or downloading something like ZEOS components. The BDE is very outdated and has a lot of quirks that will never get fixed because it is no longer maintained.
The only issue that remains is cleaning up the connection if Form1
is closed.
If it's your main form it really does not matter because your whole application will go down.
If it's not your main form than you'll need to disable closing the form by filling the OnCanClose
handler.
TForm1.CanClose(Sender: TObject; var CanClose: boolean);
begin
CanClose:= thrd.Finished;
end;
Upvotes: 3
Reputation: 19106
You should prevent any action (user and program) in the MainThread without blocking it. This can easily be done by a modal form, that cannot be closed by the user.
The thread can do anything as long as it takes and the final (synchronized) step is to close that modal form.
procedure OpenDataSetInBackground( ADataSet : TDataSet );
var
LWaitForm : TForm;
begin
LWaitForm := TForm.Create( nil );
try
LWaitForm.BorderIcons := []; // no close buttons
TThread.CreateAnonymousThread(
procedure
begin
try
ADataSet.Open;
finally
TThread.Synchronize( nil,
procedure
begin
LWaitForm.Close;
end );
end;
end );
try
LWaitForm.ShowModal;
finally
LWorkThread.Free;
end;
finally
LWaitForm.Free;
end;
end;
But you have to be careful with this and you should never try do start more than one parallel thread with this code unless you really know, what you are doing.
Upvotes: 1