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 Wait for 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
19-Nov-02
Category
Win API
Language
Delphi 2.x
Views
80
User Rating
No Votes
# Votes
0
Replies
0
Publisher:
DSP, Administrator
Reference URL:
DKB
			 Author: Lou Adler

I've created an application that spawns a couple of threads at once when a user 
presses a button. The threads execute fairly quickly, but I don't want the user to 
move on to the next task until the threads are absolutely finished. I could move 
the thread code back into the main unit, but that defeats the whole purpose of 
unlocking my user interface while the processes take place. Is there a good way to 
do this?

Answer:

In past articles regarding threads, I've discussed Critical Sections and mutexes as 
ways of protecting shared resources, and having them wait until a resource is 
freed. But how do you wait for a thread or threads to finish from the user 
interface?

There are a couple of ways to approach this. First, you create a global Boolean 
variable and use it as a flag. You set its value when the thread starts, then set 
its value when the thread ends. Meanwhile, you have can set up a looping mechanism 
in the main unit to periodically check the completion status of the thread and use 
the Application.ProcessMessages call to keep your application alive. Here's some 
code:

1   unit wthread;
2   
3   interface
4   
5   uses
6     Classes, Windows;
7   
8   type
9     TTestThr = class(TThread)
10    private
11      { Private declarations }
12    protected
13      procedure Execute; override;
14    end;
15  
16  implementation
17  uses Main;
18  { TTestThr }
19  
20  procedure TTestThr.Execute;
21  var
22    I: Integer;
23  begin
24    for I := 0 to 39 do
25      Sleep(500);
26    ImDone := True;
27  end;
28  
29  end.


The code above is the thread code. All it does is perform a for loop and wait for 
half a second in between increments. Pretty basic stuff. Here's the OnClick method 
for a button on the main form that starts the thread:

30  procedure TForm1.Button1Click(Sender: TObject);
31  begin
32    ImDone := False;
33    Label1.Caption := 'Waiting';
34    TTestThr.Create(False);
35    while not ImDone do
36      Application.ProcessMessages;
37    Label1.Caption := 'Not Waiting';
38  end;


The var ImDone is a Boolean flag that gets set as soon as the button is pressed. As 
you can see, the while loop in the OnClick handler just performs a yield with 
Application.ProcessMessages to process user input. Pretty simple right? Okay, now, 
let me tell you one very important thing:

Disregard everything that I just wrote! It's the wrong way to do it!

Why? For one thing, it's totally inefficient. While Application.ProcessMessages 
allows a program to continue to receive and process messages it receives, it still 
eats up CPU time because all it's doing is yielding temporarily, then going back to 
its original state, meaning it constantly activates and deactives. Yikes! Not good. 
With a thread like the TTestThr that I just wrote, you won't notice much of a 
difference in performance by using this methodology. But that doesn't mean that 
it's right. Furthermore, if you're a real stickler for good structured programming 
methodologies, you should avoid global variables like the plague! But that's not 
even the worst of it. There's a real big catch here...

What do you do in the case of having to wait for multiple threads to finish? In the 
model described above, you would have to create a Boolean flag for EVERY thread 
that you create! Now that's bad.

The best way to handle this condition is to create what might be called a wait 
thread - an intermediary thread that will perform the waiting. In combination with 
a couple of Windows API calls, you can achieve a very efficient thread waiting 
process at little cost to CPU time and system resources.

Those couple of Windows API functions are called WaitForSingleObject and 
WaitForMultipleObjects. These two functions are used to wait for objects to enter a 
signaled state before they return. Okay, what's signaled mean anyway? This one took 
me awhile to understand, but for you, I'll be as clear as possible so you can 
understand it much quicker than I did. Essentially, when an object is created in 
Windows, it is given a system assigned state property of sorts. While it is active 
or in use, it is said to be non-signaled. When it is available, it is said to be 
signaled. I know, it seems kind of backwards. But that's it in a nutshell. Anyway, 
with respect to the functions above, they are designed to wait for an object or 
objects to enter a signaled state; that is, wait for the objects to become 
available to the system again.

The advantage of these two functions with respect to waiting for threads to finish 
is that they enter into an efficient sleep state that consumes very little CPU 
time. Contrast that with the Application.ProcessMessages methodology which has the 
potential for consuming CPU cycles, and you know why they'd be the choice make if 
you're going to wait for threads.

WaitForSingleObject takes two parameters, a THandle and a timeout value in 
milliseconds. If you're going to wait for a single thread, all you need to do is 
pass the thread's handle and the time amount of time to wait for the object and 
it'll do the waiting for you. I should mention that there's a special system 
constant called INFINITE that will make the function wait indefinitely. Typically, 
you'll use this constant as opposed to setting a specific time. But that also 
depends on your process. Here's an example:

WaitForSingleObject(MyThread.Handle, INFINITE);

On the other hand, WaitForMultipleObjects is a bit more complex. It takes for 
arguments for parameters. They're described in the table below (don't worry about 
them too much right now, we'll discuss them below):

Argument	Type	Description
cObject	DWORD	Number of handles in the object handle array
lphObjects	Pointer	Address of object handle array
fWaitAll	Boolean	True indicates that the function waits until all objects are 
signaled
dwTimeOut	DWORD	Number of milliseconds to wait (can be INFINITE)

What's this about a handle array? Well, in order for the function to track the 
states of all objects to be waited for, their handles have to be in an array. This 
is simple to create: just declare an array of THandle and set each element's value 
to a thread's handle. No big deal. But I think it's probably best to put all this 
perspective with some code that we can discuss. Here's the entire unit code that 
contains both the wait thread's declaration and the TTestThr declaration:

39  unit wthread;
40  
41  interface
42  
43  uses
44    Classes, Windows;
45  
46  //"Worker thread declaration"
47  type
48    TTestThr = class(TThread)
49    private
50      { Private declarations }
51    protected
52      procedure Execute; override;
53    end;
54  
55    //Wait thread declaration
56  type
57    TWaitThr = class(TThread)
58    private
59      procedure UpdateLabel; //this just sets the label's caption
60    protected
61      procedure Execute; override;
62    end;
63  
64  implementation
65  uses Main;
66  { TTestThr }
67  
68  procedure TTestThr.Execute;
69  var
70    I: Integer;
71  begin
72    FreeOnTerminate := True;
73    for I := 0 to 39 do
74      Sleep(500);
75    ImDone := True;
76  end;
77  
78  procedure TWaitThr.UpdateLabel;
79  begin
80    Form1.Label1.Caption := 'Not Waiting';
81  end;
82  
83  procedure TWaitThr.Execute;
84  var
85    hndlArr: array[0..4] of THandle;
86    thrArr: array[0..4] of TTestThr;
87    I: Integer;
88  begin
89    FreeOnTerminate := True;
90    for I := 0 to 4 do
91    begin
92      thrArr[I] := TTestThr.Create(False);
93      hndlArr[I] := thrArr[I].Handle;
94      Sleep(1000); //stagger creation of the threads
95    end;
96    WaitForMultipleObjects(5, @thrArr, True, INFINITE);
97    Synchronize(UpdateLabel);
98  end;
99  
100 end.


I put the Execute method for the TWaitThr in boldface so you can focus in on it. 
Notice that to fill in the hndlArr elements, I use a simple for loop. To make 
matters even simpler, I just declared an array of TTestThr to I could create the 
threads and immediately assign their respective handles to the handle array. The 
most important line in the code is:

WaitForMultipleObjects(5, @thrArr, True, INFINITE);

which is the call to WaitForMultipleObjects. Notice too that I pass the handle 
array's address to the function, as that is what the function calls for. Once the 
call is made, it won't allow the Execute method to continue until all the threads 
enter a signaled state. Once it's done waiting, UpdateLabel is called to change the 
text of a label on my main form. Here's the entire code listing for the main form. 
All it has on it are a TLabel and two TButtons.

101 unit main;
102 
103 interface
104 
105 uses
106   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
107   StdCtrls;
108 
109 type
110   TForm1 = class(TForm)
111     Label1: TLabel;
112     Button1: TButton;
113     Button2: TButton;
114     procedure Button1Click(Sender: TObject);
115     procedure Button2Click(Sender: TObject);
116   private
117     { Private declarations }
118   public
119     { Public declarations }
120   end;
121 
122 var
123   Form1: TForm1;
124   ImDone: Boolean;
125 
126 implementation
127 uses wthread;
128 {$R *.DFM}
129 
130 procedure TForm1.Button1Click(Sender: TObject);
131 begin
132   ImDone := False;
133   Label1.Caption := 'Waiting';
134   TTestThr.Create(False);
135   while not ImDone do
136     Application.ProcessMessages;
137   Label1.Caption := 'Not Waiting';
138 end;
139 
140 procedure TForm1.Button2Click(Sender: TObject);
141 begin
142   Label1.Caption := 'Waiting';
143   TWaitThr.Create(False);
144 end;
145 
146 end.


So why go to all this trouble? Why not move the WaitForMultipleObjects code to the 
main form? The reason for this is simple. Since both WaitForSingleObject and 
WaitForMultipleObjects don't return until the object(s) they're waiting for enter a 
signaled state, the main form would essentially become locked and unavailable until 
the function returns. Kind of defeats the whole purpose of writing multi- threaded 
programs don't you think?

So here's another thing that you can add to your arsenal of multi-threading techniques...

			
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