如何利用线程等待某个事件指定的时间?
answerer: Peter Below (TeamB)
下面代码未经测试:
unit Unit2 ;
interface
uses
Windows , Classes ;
type
TDemoThread = class ( TThread )
private
{ Private declarations }
FEvent : THandle ;
FTimeout : DWORD ;
protected
procedure Execute ; override ;
procedure Action ; virtual ;
public
Constructor Create ( CreateSuspended : Boolean ; aTimeoutInSeconds : Cardinal );
Destructor Destroy ; override ;
Procedure WakeAndStop ;
end ;
implementation
{ TDemoThread }
procedure TDemoThread . Action ;
begin
// place code to execute after timeout here
end ;
constructor TDemoThread . Create ( CreateSuspended : Boolean ;
aTimeoutInSeconds : Cardinal );
begin
FTimeout := aTimeoutInSeconds * 1000 ; // store as milliseconds
FEvent := Windows . CreateEvent ( nil , false , false , nil );
/// 在这里可以把FEvent替换成你自己需要的东西
// event resets automatically and starts non-signalled
inherited Create ( CreateSuspended );
end ;
destructor TDemoThread . Destroy ;
begin
Terminate ;
SetEvent ( FEvent );
inherited ;
end ;
procedure TDemoThread . Execute ;
Var
res : DWORD ;
begin
While not Terminated Do Begin
res := WaitForSingleObject ( Fevent , FTimeout );
If res = WAIT_TIMEOUT Then
Action
Else
Break ;
End ;
end ;
procedure TDemoThread . WakeAndStop ;
begin
SetEvent ( FEvent );
end ;
end .
---------------------------------------
如果要等待一个线程结束,可以这样做:
在线程得私有变量中申明:
FEvent:TSimpleEvent;
published部分:
property Event:TSimpleEvent read FEvent;
Create中:
FEvent:=TSimpleEvent.Create;
Destroy中:
FEvent.Free;
在Execute中的第一行:
FEvent.ResetEvent;
在线程得结束(一般是最后一行代码)后添加:
FEvent.SetEvent;
然后在另外需要等待线程结束的地方把原来的
WaitForSingleObject(AThread.Handle,????)改成WaitForSingleObject(AThread.Event.Handle)即可。
---------------------------------------
Waiting for Threads
--------------------------------------------------------------------------------
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?
--------------------------------------------------------------------------------
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:
unit wthread;
interface
uses
Classes, Windows;
type
TTestThr = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
end;
implementation
uses Main;
{ TTestThr }
procedure TTestThr.Execute;
var
I : Integer;
begin
for I := 0 to 39 do
Sleep(500);
ImDone := True;
end;
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:
procedure TForm1.Button1Click(Sender: TObject);
begin
ImDone := False;
Label1.Caption := 'Waiting';
TTestThr.Create(False);
while NOT ImDone do
Application.ProcessMessages;
Label1.Caption := 'Not Waiting';
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:
unit wthread;
interface
uses
Classes, Windows;
//"Worker thread declaration"
type
TTestThr = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
end;
//Wait thread declaration
type
TWaitThr = class(TThread)
private
procedure UpdateLabel;//this just sets the label's caption
protected
procedure Execute; override;
end;
implementation
uses Main;
{ TTestThr }
procedure TTestThr.Execute;
var
I : Integer;
begin
FreeOnTerminate := True;
for I := 0 to 39 do
Sleep(500);
ImDone := True;
end;
procedure TWaitThr.UpdateLabel;
begin
Form1.Label1.Caption := 'Not Waiting';
end;
procedure TWaitThr.Execute;
var
hndlArr : Array[0..4] of THandle;
thrArr : Array[0..4] of TTestThr;
I : Integer;
begin
FreeOnTerminate := True;
for I := 0 to 4 do begin
thrArr[I] := TTestThr.Create(False);
hndlArr[I] := thrArr[I].Handle;
Sleep(1000); //stagger creation of the threads
end;
WaitForMultipleObjects(5, @thrArr, True, INFINITE);
Synchronize(UpdateLabel);
end;
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.
unit main;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TForm1 = class(TForm)
Label1: TLabel;
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
ImDone : Boolean;
implementation
uses wthread;
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
begin
ImDone := False;
Label1.Caption := 'Waiting';
TTestThr.Create(False);
while NOT ImDone do
Application.ProcessMessages;
Label1.Caption := 'Not Waiting';
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
Label1.Caption := 'Waiting';
TWaitThr.Create(False);
end;
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...
Copyright ?1997 Brendan V. Delumpa All Rights Reserved