Articles   Members Online:
-Article/Tip Search
-News Group Search over 21 Million news group articles.
-Delphi/Pascal
-CBuilder/C++
-C#Builder/C#
-JBuilder/Java
-Kylix
Member Area
-Home
-Account Center
-Top 10 NEW!!
-Submit Article/Tip
-Forums Upgraded!!
-My Articles
-Edit Information
-Login/Logout
-Become a Member
-Why sign up!
-Newsletter
-Chat Online!
-Indexes NEW!!
Employment
-Build your resume
-Find a job
-Post a job
-Resume Search
Contacts
-Contacts
-Feedbacks
-Link to us
-Privacy/Disclaimer
Embarcadero
Visit Embarcadero
Embarcadero Community
JEDI
Links
How to run queries in threads Turn on/off line numbers in source code. Switch to Orginial background IDE or DSP color Comment or reply to this aritlce/tip for discussion. Bookmark this article to my favorite article(s). Print this article
28-Nov-02
Category
Win API
Language
Delphi 2.x
Views
149
User Rating
No Votes
# Votes
0
Replies
0
Publisher:
DSP, Administrator
Reference URL:
DKB
			Author: Lou Adler 

I have several programs that run large queries, and would like to move the query 
processing to one or more background threads. How can I implement this in my 
program?

Answer:

NOTE: It's tough to discuss this subject without providing some background 
information. Threaded programming is so new to many programmers, I'd be remiss in 
my duties as The Delphi Pro if I didn't cover the most fundamental subjects of 
threaded programming. If you already have a good idea of the fundamentals of 
creating and running threads with the TThread component, you can skip to the 
section that deals with running queries in threadsBOOK1. -- The Delphi Pro

Process and Thread Basics

Many programmers, especially those new to programming with multiple threads, 
believe threads are the sole domain of programming gurus. But it's all a matter of 
understanding some fundamental concepts about how threads work in Delphi and how 
you can apply threads in your code.

If what you're programming has to do with the User Interface, you don't want to use 
multiple threads because threads require resources. In fact, every time you create 
a thread, it gets the same size stack as the main thread of a program (we'll 
discuss this below). If you have a lot of threads, you'll take up a lot of 
resources. So the idea is to be judicious in your use of threads. It may be 
tempting to create a bunch of threads to handle a bunch of different tasks because 
creating threads, as you will see, is fairly easy. But you don't want to create 
threads just for the sake of creating threads. In addition to taking up resources, 
every executing thread creates another time slice on the CPU, forcing it to handle 
more tasks. The net result is that the computer will slow way down. But there are 
some instances in which running background threads makes a lot of sense:

It's ideal to create background threads when:

your program will execute a long process like a huge query or complex calculation 
that will take several seconds or minute to execute. In single-threaded programs in 
Win16 and Win32 alike, the interface becomes totally unresponsive if you execute a 
long process. Creating a separate thread of execution to run in the background 
frees up the user interface.
your program runs in a multi-processor system. Windows NT, for example, has the 
ability to work SMP systems. With multi-threaded programs, individual threads may 
be executed on different processors, which means you take full advantage of 
balancing processor load.
your program will need to execute a few processes at a time. One caveat: If you're 
running on a single CPU system and if the processes your program will be executing 
are pretty complex and will require lots of CPU cycles, it doesn't make sense to 
have several simultaneous threads running. However, I've found that with smaller 
operations that can execute in a few seconds, it's a pretty nice feature to have.

In Windows 95 and NT (I'll refer to both systems as Win32 throughout this article), 
every program loaded into memory is called a process. Many people new to threads 
(including myself) make the mistake of believing that the definition of a process 
is interchangeable with that of a thread. It's not.

Processes are their own entities. Though the name "processes" implies a form of 
activity, a process does nothing. It is merely a memory address placeholder for its 
threads and a space where executable code gets loaded.

A process' thread is what actually does the work in a program. When a process is 
created in Win32, it automatically has an execution thread associated with it. This 
is called the main thread of the program. Other threads can be instantiated within 
a process, but you won't see many programs using multiple threads of execution. A 
thread can only be associated with one process, but a process can have many 
threads. Therefore, there is a distinct one-way, one-to-many relationship between 
processes and threads.

The TThread Object

Traditionally, processes (executing programs) are created in Win32 using the WinAPI 
CreateProcess and threads are created using CreateThread. In fact, many advanced 
Delphi and Windows programmers I've spoken with say that using the WinAPI call is 
their method of preference. With Delphi 2.0, the Delphi team created a wrapper 
class called TThread that encapsulates the WinAPI thread calls. TThread provides 
developers with a programmatic interface for creating multiple threads of execution 
in their programs. It also makes the job of creating and maintaining threads easier 
than directly using WinAPI calls.

Does this come at a price? I don't know. I have several multithreaded applications 
using both straight WinAPI calls and the TThread object and haven't noticed any 
significant differences. But my test arena was not as wide as it should have been 
to accurately gauge performance differences.

Most of the VCL is not thread-safe -- it's very important to take this into 
consideration when creating multiple threads of execution. If you call a VCL object 
from within a thread, most likely you'll raise an exception, because many of the 
VCL objects were not written with any type of synchronization code to ensure data 
integrity when called at random times from anything but the main thread. 
Essentially, they can only receive messages from a single thread. If they get a 
message from another thread, they'll hiccup, and your program will probably crash. 
Fortunately, TThread has a very simple way of safely making calls into the VCL that 
we'll discuss in a bit.

Let's look at the TThread's structure.

Here's the declaration for the TThread object:

1   TThread = class
2   private
3     FHandle: THandle;
4     FThreadID: THandle;
5     FTerminated: Boolean;
6     FSuspended: Boolean;
7     FMainThreadWaiting: Boolean;
8     FFreeOnTerminate: Boolean;
9     FFinished: Boolean;
10    FReturnValue: Integer;
11    FOnTerminate: TNotifyEvent;
12    FMethod: TThreadMethod;
13    FSynchronizeException: TObject;
14    procedure CallOnTerminate;
15    function GetPriority: TThreadPriority;
16    procedure SetPriority(Value: TThreadPriority);
17    procedure SetSuspended(Value: Boolean);
18  protected
19    procedure DoTerminate; virtual;
20    procedure Execute; virtual; abstract;
21    procedure Synchronize(Method: TThreadMethod);
22    property ReturnValue: Integer read FReturnValue write FReturnValue;
23    property Terminated: Boolean read FTerminated;
24  public
25    constructor Create(CreateSuspended: Boolean);
26    destructor Destroy; override;
27    procedure Resume;
28    procedure Suspend;
29    procedure Terminate;
30    function WaitFor: Integer;
31    property FreeOnTerminate: Boolean read FFreeOnTerminate write FFreeOnTerminate;
32    property Handle: THandle read FHandle;
33    property Priority: TThreadPriority read GetPriority write SetPriority;
34    property Suspended: Boolean read FSuspended write SetSuspended;
35    property ThreadID: THandle read FThreadID;
36    property OnTerminate: TNotifyEvent read FOnTerminate write FOnTerminate;
37  end;


Its structure is quite simple -- and simple is good

In most components there are only a few procedures and properties you need to think 
about; this is not an exception with TThread. The only methods you'll need to worry 
about are Execute, Create, and Synchronize; and the only property that you'll 
usually need to access is FreeOnTerminate.

Key Methods and Properties of TThread

The key methods and property of TThread are listed below in Table 1.

Table 1 -- Key Attributes of TThread

Attribute: Create			Parameters: CreateSuspended: Boolean
The Create method allocates memory, starts the thread and specifies the thread 
function in CreateThread as the Execute procedure. Here, as in any Create method, 
you can initialize vars and perform some preliminary operations. However, unlike a 
normal Create method, the thread is already executing by the time the method ends. 
Usually this isn't a problem because you'll just create the object and forget about 
it. But if you have to do any processing before the thread starts executing, set 
the CreateSuspended parameter to False. This allocates memory for the thread and 
sets it up, but the thread will not execute until you make a call to the Resume 
procedure. This is useful, especially if you need to set up values your thread will 
need over the course of its lifetime.

Attribute: Execute			Parameters: None
Execute is your TThread's central execution method. It's fairly synonymous with a 
main processing or central control procedure or function you might use to execute 
all the procedures in a particular unit. The only difference is that with a TThread 
object, the first method that is called must be called Execute. This doesn't mean 
that you can't call another procedure which acts in that capacity from Execute. 
It's definitely more convenient to put your execution code here because that's 
Execute's purpose.

One important note: If you look in the object declaration of TThread, you'll see 
that it is declared as a virtual; abstract; method. This means it's not implemented 
in any way, shape or form; therefore, it's up to you to provide the code. And 
there's no inherited functionality, so you'll never make a call to inherited 
Execute; in your own implementation.

Attribute: Synchronize			Parameters: Method:TThreadMethod 
Synchronize is your key to safely accessing the VCL in your thread. When 
Synchronize executes, your thread becomes a part of the main thread of the program, 
and in the process, suspends the operation of the main thread. This means the VCL 
can't receive messages from other threads other than the one you're synchronizing 
to the main thread, which in turn makes it safe to execute any VCL calls.

Think of Synchronize as a middleman in a transaction. You have a buyer, which is 
the main thread of the program, and a seller of services, which is another thread 
of execution created within the same process. The seller would like to sell the 
buyer some goods -- in our case, do some operation on the VCL components running in 
the main thread -- but the buyer doesn't really know the seller, and is appalled at 
how the seller actually performs his service, so is afraid the seller's service may 
have a negative effect on him. So the seller enlists the help of an agent (the 
Synchronize procedure) to smooth things out, take the buyer out to lunch so the 
seller can do his thing.

The seller is a procedure that performs the action on behalf of the thread. It 
doesn't have to be a specific type, but it must be a method of the thread. Say I 
have a long process running in a background thread that at certain times must 
update the text in a TPanel on the main form to give feedback about the thread's 
current status to the user. If I wrote in my code Form1.Panel1.Caption := 'This is 
my update message', I'd raise an exception -- most likely an access violation 
error. But if I encapsulate the call in a procedure, then call Synchronize, the 
message will be sent and text will be updated. Synchronize acted as a middleman so 
my status message could be passed to the TPanel.

I've may have confused rather than enlightened you! Just remember this:

When you want to call VCL objects from another thread, create a wrapper procedure 
for the operations you want to carry out, then use Synchronize to synchronize your 
thread with the main thread so your call can be safely executed.

Synchronize is meant to be used really quickly. Execute it, then get out of it as 
soon as you can. While Synchronize is running, you essentially have only one thread 
working. The main thread is suspended.

Attribute: FreeOnTerminate			Parameters: Set property to Boolean value
This is a really useful property. Typically, once your execute procedure finishes, 
the Terminated Boolean property is set to False. However, the thread still exists. 
It's not running, but it's taking up space, and it's up to you to free the thread. 
But by setting this property in the Create constructor to True, the thread is 
immediately freed -- and with it, all the resources it took up -- after it's 
finished executing.

A Real-life Example

Most people get more out of seeing code examples to implement a certain concept. 
Below are code excerpts from a program that I wrote that performs a bunch of 
queries in sequence. First we have the type declaration:

38  type
39    TEpiInfoProc = class(TThread)
40      {Base processing class for Episode Information processing}
41    private
42      FStatMsg: string;
43      FSession: TSession;
44      tblPrimary,
45        tblHistory,
46        tblSymmetry: string;
47      FIter: Integer;
48      property Iteration: Integer read FIter write FIter;
49      procedure eiCreateEpiTemp1; //Performs initial joins
50      procedure eiCreateEpiTemp2; //Creates new table of summarized info
51      procedure eiGetClassifications(clState, //'AMI', 'Asthma', etc.
52        clName, //'H1', 'H2', etc.
53        priFld, //Join field from Primary
54        hstFld: string; //Join field from History
55        bMode: TBatchMode); //Batch Mode (will always be
56      //batCopy 1st time);
57      procedure eiCreateEpiInfoTable(clSrc, clDst, clName: string);
58      procedure eiCreateHistory(HistIndicator: string);
59  
60      //Generic processing methods
61      procedure EnableBtns;
62      procedure GetTableNames;
63      procedure UpdStatus;
64      procedure IndexTbl(dbName, tblName, idxName, fldName: string; idxOpts:
65        TIndexOptions);
66    protected
67      procedure Execute; override;
68    public
69      constructor Create;
70    end;


The above is like anything you see in Delphi when you declare a descendant of a 
class. You declare your variables, properties, and methods just like anything else.

Here's the Create constructor for the thread:

71  constructor TEpiInfoProc.Create;
72  begin
73    inherited Create(True);
74    FSession := TSession.Create(Application);
75    with FSession do
76    begin
77      SessionName := 'EpiInfoSession';
78      NetFileDir := Session.NetFileDir;
79      PrivateDir := 'D:\EpiPriv';
80    end;
81    FreeOnTerminate := True;
82    Resume;
83  end;


Notice I'm creating a new TSession instance. When we discuss running queries in 
threads, I'll go into more depth about this. At this point the important thing to 
note is that I create the thread in a suspended state by calling the inherited 
Create and setting its CreateSuspended parameter to True. This allows me to perform 
the code immediately below the inherited Create before the thread code actually 
starts running. Now, on to the Execute procedure.

84  procedure TEpiInfoProc.Execute;
85  var
86    N, M: Integer;
87  begin
88    try
89      Synchronize(EnableBtns);
90        //Set enabled property of buttons to opposite of current state
91      Synchronize(GetTableNames); //Get Names
92      FStatMsg := 'Now performing initial summarizations';
93      Synchronize(UpdStatus);
94      ShowMessage('Did it!');
95      Exit;
96      eiCreateEpiTemp1;
97      eiCreateEpiTemp;
98      {Create H1 - H9 tables}
99      for N := 0 to 8 do
100       for M := 0 to 5 do
101       begin
102         FStatMsg := 'Now performing ' + arPri[M] + ' field extract for ' + arDis[N];
103         Synchronize(UpdStatus);
104         Iteration := M;
105         //first iteration must be a batCopy, then batAppend thereafter
106         if (M = 0) then
107           eiGetClassifications(arDis[N], arCls[N], arPri[M], arHst[M], batCopy)
108         else
109           eiGetClassifications(arDis[N], arCls[N], arPri[M], arHst[M], batAppend);
110       end;
111 
112     {Now do Outer Joins}
113     for N := 0 to 8 do
114     begin
115       FStatMsg := 'Now creating ' + arDst[N];
116       Synchronize(UpdStatus);
117       eiCreateEpiInfoTable(arSrc[N], arDst[N], arCls[N]);
118     end;
119 
120     IndexTbl('EPIINFO', 'EpiInfo', 'Primary', 'Episode', [ixPrimary]);
121 
122     for N := 0 to 8 do
123       eiCreateHistory(arCls[N]);
124 
125     FStatMsg := 'Operation Complete!';
126     Synchronize(UpdStatus);
127     Synchronize(EnableBtns);
128   except
129     Terminate;
130     Abort;
131   end;
132 end;


Notice all my calls to Synchronize, especially the synchronized call to UpdStatus. 
Immediately preceding this call, I set a variable called FStatMsg to some text. 
UpdStatus uses this text to set the SimpleText of a TStatusBar on the main form of 
the program.

Why not just pass this as a string variable parameter to UpdStatus? Because 
synchronized methods cannot have parameters. If you need to pass parameters, you 
must either set them in variables or create a structure to hold values your 
synchronized method can then access and pass to your main form.

Also take note of the looping structures I've written. You might be tempted to run 
a loop within a synchronized method. Although your code will work, this defeats the 
purpose of multiple threads because of what we discussed above. Synchronization 
makes your thread part of the main thread during the time it is executing, meaning 
you have only one thread actually running. This reduces your program to a single 
thread of execution. And if you have a potentially long process like several 
sequential queries executed from within a loop like I have above, forget it! Your 
program will act just like a single-threaded application until you're out the loop.

Running Queries in Threads

In order to successfully run queries in threads, you must follow a few cardinal 
rules:

For each thread you create that will be executing queries, you must have a separate 
instance of a TSession created along with the thread. (See Create constructorBOOK2 
above)

Simply put, TSessions essentially define the multi-threadness of the BDE. The BDE 
requires separate TSessions to allow multiple access to shared resources. Failing 
to do this will cause some very strange things to occur in your program (it will 
crash with an BDE exception of some sort.)

Every data-access VCL component you instantiate within a thread must have its 
SessionName property set to that of the SessionName of the TSession created with 
your thread.

If you don't do this, the data-access component will default to the TSession 
created along with the main thread; this could have serious ramifications. If you 
assign components to a new TSession and forget to do so for others, when the 
components interact with each other with let's say a TBatchMove moving data from a 
query to a TTable, you'll get an error that says you're trying to perform an 
operation on objects in different sessions. Not good. So just to ensure that 
everything is safe, keep the data-access components you create in separate threads 
distinct by assigning the SessionNames to the SessionName of the TSession you 
create along with your thread.

You must create synchronized methods for any operation that will be accessing 
data-access components embedded on a form.

I can't stress enough the importance of performing any call that will access VCL 
components within a Synchronize call. You're looking for trouble if you don't. 
Let's say you run a background query that will provide information in a visual form 
in a TDBGrid. Running the query is the easy part. But once you're finished, you 
have to have a way of manipulating a TDataSource so that its DataSet property will 
point to the query you've run in the thread. Setting this must be done in a call to 
a synchronized method that does the setting for you.

Building on the previous rule, queries that will be providing data for data-aware 
components such as a TDBGrid must be persistent.

Once you free a thread, its resources are freed too. If you create a TQuery within 
the context of a TThread, your query is gone once the thread terminates and frees. 
The easiest thing to do then is to drop a TQuery on a form and run it from within 
the thread. That way, its handle remains valid even though the thread has finished 
its work.

Of the above rules, 1 and 2 are the most important elements for successfully 
running queries in separate threads of execution. While rules 3 and 4 are 
important, under certain conditions your queries will not run if you don't obey 
rules 1 and 2.

"Visual" and Non-visual Background Queries

Now that we've established ground rules regarding running queries in threads, we 
must take into consideration a couple of implementation paths. I put the visual in 
quotes above to denote queries whose results will be displayed in a data-aware 
component of some sort. Non-visual background queries don't provide any visual 
result. They just run, write to persistent data stores and return. How you 
implement these methods is significantly different -- we'll discuss them in 
separate sections below.

In either of these cases, the aim is to free up the user interface. One of my 
biggest frustrations with writing programs that process a lot of information is 
that once I've started execution, I can't do anything with the interface. I can't 
minimize or maximize the application; I can't move the window. Ever since I've 
learned to implement threaded technology in my programs, I need not worry about 
that. It's a real boon to my productivity.

Running Totally Background Queries

This method is a challenge; you have to code everything yourself. It's not a lot of 
code, but there are certain things to consider that you can take for granted when 
you drop VCL components onto a form. This method is very useful for "Data Mining" 
types of programs, in which you're querying huge tables and coming up with highly 
refined, summarized result sets. The operations that typify this type of 
application are several queries performed in succession. Were you to run this kind 
of application in a single-threaded program, you can effectively kiss your 
productivity goodbye because your user interface will be locked.

The example I'll be using for this discussion is the exampleBOOK3 I used aboveBOOK3 
because that application is a data mining type of application. It was built to run 
several sequential queries against Paradox tables with hundreds of thousands of 
records (pretty big for Paradox tables).

Let's revisit the type declaration of thread:

133 type
134   TEpiInfoProc = class(TThread)
135     {Base processing class for Episode Information processing}
136   private
137     FStatMsg: string;
138     FSession: TSession;
139     tblPrimary,
140       tblHistory,
141       tblSymmetry: string;
142     FIter: Integer;
143     property Iteration: Integer read FIter write FIter;
144     procedure eiCreateEpiTemp1; //Performs initial joins
145     procedure eiCreateEpiTemp2; //Creates new table of summarized info
146     procedure eiGetClassifications(clState, //'AMI', 'Asthma', etc.
147       clName, //'H1', 'H2', etc.
148       priFld, //Join field from Primary
149       hstFld: string; //Join field from History
150       bMode: TBatchMode); //Batch Mode (will always be
151     //batCopy 1st time);
152     procedure eiCreateEpiInfoTable(clSrc, clDst, clName: string);
153     procedure eiCreateHistory(HistIndicator: string);
154 
155     //Generic processing methods
156     procedure EnableBtns;
157     procedure GetTableNames;
158     procedure UpdStatus;
159     procedure IndexTbl(dbName, tblName, idxName, fldName: string; idxOpts:
160       TIndexOptions);
161   protected
162     procedure Execute; override;
163   public
164     constructor Create;
165   end;


There's something in the type declaration that I didn't bring up previously. Notice 
the second private variable FSession. Then, look at the constructor Create code 
below:

166 constructor TEpiInfoProc.Create;
167 begin
168   inherited Create(True);
169   FSession := TSession.Create(Application);
170   with FSession do
171   begin
172     SessionName := 'EpiInfoSession';
173     NetFileDir := Session.NetFileDir;
174     PrivateDir := 'D:\EpiPriv';
175   end;
176   FreeOnTerminate := True;
177   Resume;
178 end;


I simply instantiate a new session in my Create constructor for my queries and 
tables to point to during the course of execution. If you don't have the latest 
update to Delphi and you look in the help file under TSession, you're warned not to 
create a TSession on the fly. Why? Truthfully, I don't know. I broke the rules 
anyway when I originally saw this warning because after looking at the VCL source 
for the TSession object in the DB.PAS file, I didn't see anything in the TSession 
object that would lead me to believe that I couldn't instantiate a TSession object 
on the fly. This changed in 2.01 -- the help file makes no such warning -- so it's 
not an issue. Of all the things that I'm doing in the thread, creating this 
TSession is the absolute key operation because it provides a common ground for all 
the data access components that I instantiate in the thread.

During the course of executionBOOK4, I make calls to several procedures that 
perform queries on several different tables. However, they all operate similarly, 
so it's only necessary to list one of the procedures to illustrate how you should 
do your queries in a thread. Here's an example procedure:

179 procedure TEpiInfoProc.eiCreateEpiTemp1;
180 var
181   sqlEI: TEnhQuery;
182 begin
183 
184   sqlEI := TEnhQuery.Create(Application);
185   with sqlEI do
186   begin
187     SessionName := FSession.SessionName;
188     DatabaseName := 'Primary';
189     DestDatabaseName := 'PRIVATE';
190     DestinationTable := 'epitemp1.db';
191     DestBatchMoveMode := batCopy;
192     DoBatchMove := True;
193     with SQL do
194     begin
195       Clear;
196       Add('SELECT DISTINCT d.Episode, d.PatientID, d.Paid AS TotPd, d.Charge AS 
197 TotChg, D1.Start'
198       Add('FROM "' + tblPrimary + '" d LEFT OUTER JOIN "' + tblSymmetry + '" D1 ');
199       Add('ON (D1.Episode = d.Episode)');
200       Add('ORDER BY d.Episode, d.PatientID, d.Paid, d.Charge, D1.Start');
201     end;
202     try
203       try
204         Execute;
205       except
206         raise;
207         Abort;
208       end;
209     finally
210       Free;
211     end;
212   end;
213 end;


The important thing to notice in the code example above is that the first property 
I set for the query I create is its SessionName property. This falls in line with 
obeying rulesBOOK1 1 and 2 that I mentioned above. I did this first so I'd be in 
compliance with them right away, though you can set properties in any order. The 
whole point to this is that looking at the procedure, it's no different from any 
type of procedure you'd use to create a query and execute it in code. The only 
difference is that you don't rely on the default TSession; you use the one you 
created at construction time.

While I can't release what the application above actually does, I'll give you some 
gory details. On the average, the application requires three to four hours to 
execute completely, based on the size of the client database. Most of the client 
databases are several hundred megabytes in size, and all are in excess of 600MB per 
table, so you can imagine that with the enormity of the data sets, a single 
threaded application would just sit there and appear to be locked. But running the 
queries in the background frees the interface, so status messages can be supplied 
quite easily, and the form remains active during the run.

I took an easy way out in this program to prevent multiple threads from executing. 
Typically what you'll do is use a system mutex or a semaphore to provide a 
mechanism signaling that you have a process running. But I found it much easier to 
just disable all the buttons on the form, so the user could still move the form 
around and even enter selection criteria but couldn't execute the new process until 
the current process has finished. It took much less code to implement a disabling 
feature than to implement a system mutex.

Running Background Queries that will Produce Visible Result Sets

You won't need to do this often because typically, queries run to display data in a 
grid or some other data-aware component are usually run against smaller tables and 
execute fairly quickly, so you can get away with running a single thread of 
execution. There are some instances in which you might need to query a couple of 
unrelated tables at the same time, so running the queries in separate threads of 
execution makes a lot of sense.

Below is unit code from a test unit I wrote to illustrate this purpose. On my form 
I've dropped two of each of the following components: TQuery, TSession, TDataSource 
and a TDBGrid. I also have a button that will perform the thread creation. Let's 
look at the code, then discuss it:

214 unit thrtest;
215 
216 interface
217 
218 uses
219   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, DB,
220     DBTables,
221   Grids, DBGrids, StdCtrls;
222 
223 type
224   TForm1 = class(TForm)
225     DataSource1: TDataSource;
226     Query1: TQuery;
227     DBGrid1: TDBGrid;
228     Button1: TButton;
229     DataSource2: TDataSource;
230     Query2: TQuery;
231     DBGrid2: TDBGrid;
232     Session1: TSession;
233     Session2: TSession;
234     procedure Button1Click(Sender: TObject);
235   end;
236 
237   //Thread class declaration - very simple
238   TQThread = class(TThread)
239   private
240     FQuery: TQuery;
241   protected
242     procedure Execute; override;
243   public
244     constructor Create(Query: TQuery);
245   end;
246 
247 var
248   Form1: TForm1;
249 
250 implementation
251 
252 {$R *.DFM}
253 
254 constructor TQThread.Create(Query: TQuery);
255 begin
256   inherited Create(True); // Create thread in a suspendend state so we can prepare 
257 vars
258   FQuery := Query; //Set up local query var to be executed.
259   FreeOnTerminate := True; //Free thread when finished executing
260   Resume;
261 end;
262 
263 procedure TQThread.Execute;
264 begin
265   FQuery.Open; //Perform the query
266 end;
267 
268 procedure TForm1.Button1Click(Sender: TObject);
269 begin
270   TQThread.Create(Query1);
271   TQThread.Create(Query2);
272 end;
273 
274 end.


I've made the thread intentionally easy. The only thing you pass to the constructor 
is a query. The rest is all taken care of.

Before I did any coding of the above, I did the following with the components on 
the form:

DataSource1 and DataSource2 have their DataSet properties set to Query1 and Query2, 
respectively. Likewise, DBGrid1 and DBGrid2 have their DataSource properties set to 
DataSource1 and DataSource2, respectively.
Query1 and Query2 have their SessionName property set to the SessionName property 
of Session1 and Session2, respectively.
Both TQuerys have their DatabaseName properties set to DBDEMOS. Query1's SQL is 
simple : 'SELECT * FROM "employee.db".' Query2's SQL is simple as well: 'SELECT * 
FROM "customer.db".'

This is a really simplistic example that works amazingly well. When the user 
presses the button, the program creates two new threads of type TQThread. Since we 
pass in the query we want to execute, the thread knows which one to operate on.

Notice that I didn't put any synchronization code in this example. Some might think 
that you have to do this for a DataSource component, but the DataSource is 
Session-less, so it's impossible. Besides, the DataSource is merely a conduit 
between the Query and the DBGrid. It's fairly inert.

As in any program, you can make this much more complex. For example, you can set 
the DatabaseName and SQL of the TQuerys at runtime, making this a really flexible 
display tool. You can add a batchmove facility at the tail-end of the run so that 
in addition to displaying the results in a DBGrid, the program will also write to 
result tables. Play around with this to see how you can expand on the concept.

Conclusion

Multi-threading is a breakthrough and revolutionary programming technology for many programmers. Note, that on Unix systems, multithreading has existed for several years. But for Windows platforms, this hasn't been the case until a few years ago. Also, I didn't cover some of the really advanced stuff that you can do with threads. For that, you'll have to refer to advanced-level Win32 development books. But hopefully, in the course of this discussion, you've reached a level of understanding that will get you on your way to constructing your own multi-threaded applications.

			
Vote: How useful do you find this Article/Tip?
Bad Excellent
1 2 3 4 5 6 7 8 9 10

 

Advertisement
Share this page
Advertisement
Download from Google

Copyright © Mendozi Enterprises LLC