首页  编辑  

线程等待

Tags: /超级猛料/Thread.线程/   Date Created:

如何利用线程等待某个事件指定的时间?

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