Watch, Follow, &
Connect with Us

For forums, blogs and more please visit our
Developer Tools Community.


Welcome, Guest
Guest Settings
Help

Thread: Why is compiler not seeing VCL component inside a thread execution?


This question is answered. Helpful answers available: 1. Correct answers available: 1.


Permlink Replies: 11 - Last Post: Jul 8, 2016 7:56 AM Last Post By: Debbie Erickson
Debbie Erickson

Posts: 34
Registered: 8/14/09
Why is compiler not seeing VCL component inside a thread execution?  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jul 7, 2016 7:49 AM
Hi. This is my first foray into the wonderful world of threading. I'm using Delphi 7.

I'm doing a proof-of-concept model for displaying a dashboard report every 10 minutes. I have not yet developed the Crystal report, so I was simply going to toggle the visibility of a grid as the timer triggers the thread. When the thread executes, I would make the grid visible, and when it terminates, I would make the grid invisible.

In the thread creation, I create an instance of my ADOQuery. In the UI, I have a datasource and a dbgrid. The grid and datasource are associated in the design object properties. In the thread execution, I open the query. Immediately after that, I was trying to assign the dataset property of the datasource to the newly opened query. And it also does not like me referencing my temporary grid, either. Compiler said both are undeclared, but they are on the vcl form!

For any who want to see my current code, the line in bold is the datasource component on my form. For simplicity I removed the setting of the grid property.
type
TADOSQLThread = class(TThread)
private
FADOQuery: TADOQuery; // Internal query
FSQL: string; // SQL To execute

public
constructor Create(CreateSuspended:Boolean; AConnString:string;
ASQL:string);
destructor Destroy; override;
procedure Execute(); override;

property SQL:string read FSQL write FSQL;
property DashboardQuery:TADOQuery read FADOQuery write FADOQuery;
end;

procedure TFormMain.DashboardTimerTimer(Sender: TObject);
begin
DashboardTimer.Enabled:= False;
SpawnDashboardThread(Sender);
DashboardTimer.Enabled:= True;
end;

constructor TADOSQLThread.Create(CreateSuspended:Boolean; AConnString:String;
ASQL:string);
begin

inherited Create(CreateSuspended);

// ini
Self.FreeOnTerminate := False;

// Create the Query
FADOQuery := TAdoquery.Create(nil);
// assign connections
FADOQuery.ConnectionString := AConnString;
FADOQuery.SQL.Add(ASQL);
Self.FSQL:= ASQL;
end;

destructor TADOSQLThread.Destroy;
begin
inherited;
end;

procedure TADOSQLThread.Execute();
begin

inherited;

try
// try to open database connection
Self.FADOQuery.Open;
DataSource1.dataset:= self.FADoquery;
sleep(10000);
application.ProcessMessages;
except
showmessage('problem in thread');
end;
end;

procedure TFormMain.TerminateDashboardThread(sender: TObject);
begin
(sender as TADOSQLThread).FADOQuery.Close;
(sender as TADOSQLThread).Destroy;
end;

procedure TFormMain.SpawnDashboardThread(Sender: TObject);
var th : TThread;
ASQL : string;
begin
ASQL:= 'select * from equipment';
//create Thread
th := TADOSQLThread.Create(True, Common_DM.TheDatabase.ConnectionString, ASQL);
// thread finalization
th.OnTerminate := TerminateDashboardThread;
th.Resume;
end;

I've done some more googling and see that synchronize is a way for threads to access UI components in main thread. But in Delphi 7, it does not allow me to pass a parameter with the synchronize method call. The concept of anonymous methods does not exist in Delphi 7. So how in the world do you get data from a thread into UI on the main thread?

Edited by: Debbie Erickson on Jul 7, 2016 9:04 AM
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: Why is compiler not seeing VCL component inside a thread execution?[Edit]
Helpful
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jul 7, 2016 10:09 AM   in response to: Debbie Erickson in response to: Debbie Erickson
Debbie wrote:

I'm doing a proof-of-concept model for displaying a dashboard report
every 10 minutes. I have not yet developed the Crystal report, so I
was simply going to toggle the visibility of a grid as the timer
triggers the thread. When the thread executes, I would make the grid
visible, and when it terminates, I would make the grid invisible.

Make sure the thread synchronizes with the main UI thread to perform that
toggle. It is not safe to access UI controls from outside the main UI thread.
When the thread starts running, use TThread.Synchronize() or TThread.Queue()
to make the grid visible. When the thread terminats, you can use TThread.Synchronize()/Queue(),
or you can use the TThread.OnTerminate event, which is already synchronized.

In the thread creation, I create an instance of my ADOQuery.
In the thread execution, I open the query.

You are creating the ADO query in the thread constructor, which means you
are creating it in the context of the main UI thread, not in the context
of the worker thread itself. ADO is based on COM and uses an apartment threading
model, so ADO objects have thread affinity. You can't create an ADO object
in one thread and use it in another thread without marshalling the COM objects
across the thread boundaries. Which TADOQuery does not do.

Immediately after that, I was trying to assign the dataset property of the
datasource to the newly opened query.

That will not work across thread boundaries. You would have to synchronize
with the main UI thread.

And it also does not like me referencing my temporary grid, either.
Compiler said both are undeclared, but they are on the vcl form!

That is not an ADO issue, that is a coding issue. DataSource1 is not a member
of your TADOSQLThread class, it is a member of your TFormMain class instead.
Your thread needs to use a pointer to the Form to reach its DataSource object,
eg:

FormMain.DataSource1.Dataset := Self.FADoquery;


Otherwise, add a TDataSource pointer to your thread class, and have the main
thread assign that pointer when creating the thread:

type
  TADOSQLThread = class(TThread)
  private
    FDataSource: TDataSource;
    ...
  public
    ...
    property DataSource: TDataSource read FDataSource write FDataSource;
  end;
 
procedure TADOSQLThread.Execute;
begin
  ...
  // use Self.FDataSource as needed...
  ...
end;
 
procedure TFormMain.SpawnDashboardThread(Sender: TObject);
var
  th : TADOSQLThread;
  ...
begin
  ...
  th := TADOSQLThread.Create(...);
  th.DataSource := DataSource1;
  ...
end;


application.ProcessMessages;

DO NOT call Application.ProcessMessages() in a worker thread, only in the
main UI thread (if at all).

showmessage('problem in thread');

DO NOT call ShowMessage() is a worker thread. It displays a VCL TForm, and
the VCL is not thread-safe. If you must display a popup message in a worker
thread, sync with the main UI thread to call ShowMessage(), otherwise use
the MessageBox() function in the Windows unit instead (which is thread-safe).

procedure TFormMain.TerminateDashboardThread(sender: TObject);
begin
(sender as TADOSQLThread).FADOQuery.Close;
(sender as TADOSQLThread).Destroy;
end;

You are setting FreeOnTerminate=False, and you are not freeing the thread
object anywhere in your code, so you are leaking it, even though it is not
running anymore. You need to Destroy/Free it when you are done using it.
Just do not do that inside the OnTerminate event handler itself as the thread
object still needs to be accessed by the RTL after the handler exits.

I've done some more googling and see that synchronize is a way for
threads to access UI components in main thread.

Yes.

But in Delphi 7, it does not allow me to pass a parameter with the
synchronize method call.

It doesn't allow that in ANY version of Delphi. It can only call a parameter-less
method. You need to store your desired parameter value as a member of your
thread class, and define a method in the class that uses that member. Then
you can pass that method to Synchronize(), and it can then do whatever you
want with the member.

type
  TADOSQLThread = class(TThread)
  private
    ...
    FValue: DataType;
    procedure DoSomethingWithValue;
    ...
  end;
 
procedure TADOSQLThread.Execute;
begin
  ...
  FValue := ...;
  Synchronize(DoSomethingWithValue);
  ...
end;
 
procedure TADOSQLThread.DoSomethingWithValue;
begin
  // use FValue as needed...
end;


--
Remy Lebeau (TeamB)
Debbie Erickson

Posts: 34
Registered: 8/14/09
Re: Why is compiler not seeing VCL component inside a thread execution?[Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jul 7, 2016 11:52 AM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Thank you, Remy. I moved the creation of the adoquery to the execute method of the thread. When it gets to the open statement, an exception is thrown. I fixed the other things you said. What am I missing now? This is what my execute looks like now:

procedure TADOSQLThread.Execute();
begin

inherited;
// Create the Query
FADOQuery := TAdoquery.Create(nil);
// assign connections
FADOQuery.ConnectionString := FConnString;
FADOQuery.SQL.Add(FSQL);
try
// try to open database connection
with self.FADOQuery do
begin
Open;
while not Eof do
begin
fstatusText:= fieldbyname('equipno').AsString;
synchronize(UpdateUIForDashboard);
next;
application.ProcessMessages;
end;
end;
except
fStatusText := 'problem in thread';
synchronize(UpdateUIForDashboard);
end;
end;

Edited by: Debbie Erickson on Jul 7, 2016 12:14 PM
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: Why is compiler not seeing VCL component inside a threadexecution?[Edit] [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jul 7, 2016 12:43 PM   in response to: Debbie Erickson in response to: Debbie Erickson
Debbie wrote:

Thank you, Remy. I moved the creation of the adoquery to the
execute method of the thread. When it gets to the open statement,
an exception is thrown.

Since ADO is based on COM, you need to have your thread initialize the COM
library before creating any ADO objects. COM must be initialized on a per-thread
basis.

Also, get rid of ProcessMessages(), it does not being in your thread at all.

Try something more like this:

procedure TADOSQLThread.Execute;
begin
  CoInitialize(nil);
  try
    // Create the Query
    FADOQuery := TADOQuery.Create(nil);
    try
      // assign connections
      FADOQuery.ConnectionString := FConnString;
      FADOQuery.SQL.Text := FSQL;
 
      // try to open database connection
      FADOQuery.Open;
      try
        while not FADOQuery.Eof do
        begin
          fstatusText := FADOQuery.FieldByName('equipno').AsString;
          Synchronize(UpdateUIForDashboard);
          FADOQuery.Next;
        end;
      finally
        FADOQuery.Close;
      end;
    finally
      FADOQuery.Free;
    end;
  finally
    CoUninitialize;
  end;
end;
 
procedure TADOSQLThread.DoTerminate;
begin
  if FatalException <> nil then
  begin
    fStatusText := 'problem in thread';
    Synchronize(UpdateUIForDashboard);
  end;
  inherited;
end;


--
Remy Lebeau (TeamB)
Debbie Erickson

Posts: 34
Registered: 8/14/09
Re: Why is compiler not seeing VCL component inside a threadexecution?[Edit] [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jul 7, 2016 1:17 PM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Hi. What using files do I need for the coinitialize statement? I am linking in comobj.pas in the uses list of interface section. Compiler still says it can't find it.
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: Why is compiler not seeing VCL component inside athreadexecution?[Edit] [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jul 7, 2016 1:37 PM   in response to: Debbie Erickson in response to: Debbie Erickson
Debbie wrote:

Hi. What using files do I need for the coinitialize statement?

CoInitialize() and CoUninitialize() are in the ActiveX unit.

--
Remy Lebeau (TeamB)
Debbie Erickson

Posts: 34
Registered: 8/14/09
Re: Why is compiler not seeing VCL component inside athreadexecution?[Edit] [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jul 7, 2016 1:52 PM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Question on the DoTerminate method in your last post. The method indicates that is a member of the thread.
So my thread class is defined as

TADOSQLThread = class(TThread)
private
FADOQuery: TADOQuery; // Internal query
FSQL: string; // SQL To execute
FConnString : string; //connection string for query
FstatusText : string;
procedure UpdateUIForDashboard;
public
constructor Create(CreateSuspended:Boolean; AConnString:string;
ASQL:string);
destructor Destroy; override;
procedure Execute(); override;
procedure TerminateDashboardThread(sender:tobject);

property SQL:string read FSQL write FSQL;
property DashboardQuery:TADOQuery read FADOQuery write FADOQuery;
end;

As far as I know, all events need to have the sender object. So in the form, I have this:
procedure TFormMain.SpawnDashboardThread(Sender: TObject);
var th : TThread;
ASQL : string;
begin
ASQL:= 'select equipno from equipment';
memo1.Clear;
memo1.Lines.Add('before creation of thread');
//create Thread
th := TADOSQLThread.Create(True, Common_DM.TheDatabase.ConnectionString, ASQL);
// thread finalization
th.OnTerminate := TADOSQLThread.TerminateDashboardThread(sender);
th.Resume;
end;

The compiler strongly objects to the onterminate line.
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: Why is compiler not seeing VCL component insideathreadexecution?[Edit] [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jul 7, 2016 2:21 PM   in response to: Debbie Erickson in response to: Debbie Erickson
Debbie wrote:

Question on the DoTerminate method in your last post. The method
indicates that is a member of the thread.

DoTerminate() is a virtual method of TThread, and can be overriden in derived
classes:

type
  TADOSQLThread = class(TThread)
    ...
  protected
    procedure Execute; override;
    procedure DoTerminate; override;
    ...
  end;


The base TThread.DoTerminate() method is what calls the OnTerminate event
handler (via Synchronize()):

procedure TThread.CallOnTerminate;
begin
  if Assigned(FOnTerminate) then FOnTerminate(Self);
end;
 
procedure TThread.DoTerminate;
begin
  if Assigned(FOnTerminate) then Synchronize(CallOnTerminate);
end;


DoTerminate() is called in the context of the worker thread, whereas OnTerminate
is called on the context of the main UI thread. Overriding DoTerminate()
is useful for performing cleanup operations for the worker thread regardless
of whether Execute() exits gracefully or due to an uncaught exception.

So my thread class is defined as
<snip>
The compiler strongly objects to the onterminate line.

As it should be, because your code is wrong.

TerminateDashboardThread() is your OnTerminate event handler. It should
be a method of your Form class that is creating the thread, not a method
of the thread class itself:

procedure TFormMain.SpawnDashboardThread(Sender: TObject);
var
  th : TADOSQLThread;
  ASQL : string;
begin
  ASQL := 'select equipno from equipment';
  memo1.Clear;
  memo1.Lines.Add('before creation of thread');
  //create Thread
  th := TADOSQLThread.Create(True, Common_DM.TheDatabase.ConnectionString, 
ASQL);
  // thread finalization
  th.OnTerminate := TerminateDashboardThread;
  th.Resume;
end;
 
procedure TFormMain.TerminateDashboardThread(Sender: TObject);
begin
  // ...
end;


--
Remy Lebeau (TeamB)
Debbie Erickson

Posts: 34
Registered: 8/14/09
Re: Why is compiler not seeing VCL component inside a threadexecution?[Edit] [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jul 7, 2016 1:41 PM   in response to: Debbie Erickson in response to: Debbie Erickson
I found the coinitialize in the activex.pas file. But it still crashes in the open of the ado query, even with the modifications you indicated.
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: Why is compiler not seeing VCL component inside athreadexecution?[Edit] [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jul 7, 2016 1:57 PM   in response to: Debbie Erickson in response to: Debbie Erickson
Debbie wrote:

I found the coinitialize in the activex.pas file. But it still
crashes in the open of the ado query, even with the
modifications you indicated.

What EXACTLY does the exception actually say?

--
Remy Lebeau (TeamB)
Debbie Erickson

Posts: 34
Registered: 8/14/09
Re: Why is compiler not seeing VCL component inside a thread execution?  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jul 7, 2016 12:21 PM   in response to: Debbie Erickson in response to: Debbie Erickson
I got a new error on the open of the adoquery.
Debbie Erickson

Posts: 34
Registered: 8/14/09
Re: Why is compiler not seeing VCL component inside a thread execution?  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jul 8, 2016 7:56 AM   in response to: Debbie Erickson in response to: Debbie Erickson
I finally got it to work. I was just throwing stuff on the wall, so to speak, to see what worked. I ended up using a slightly different connection string for my tadoquery. I'm still not sure why that fixed it. <shrug> At least I have a functioning prototype.

Remy, I'm very grateful for all your help. Thanks!
Legend
Helpful Answer (5 pts)
Correct Answer (10 pts)

Server Response from: ETNAJIVE02