Problem with WaitForSingleObject

Hello,

Please have a look at the code below.
What it should be doing is:

1. User clicks the SpawnButton
2. ('Calculation began...') info is displayed
3. Thread starts the execution- it calculates if the entered number is prime
4. Once it's finished it displays the result and SetEvent(hEvent)
5. Main Thread waits until Thread finishes the calculation. It uses WaitForSingleObject(hEvent, INFINITE).

the problem is that 'WaitForSingleObject(hEvent, INFINITE)' never returns the value. It looks like this function
just never finds out that SetEvent(hEvent) was ever called- and it should because it IS CALLED.

What do I do wrong?

Jack


unit PrimeForm;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TPrimeFrm = class(TForm)
    SpawnButton: TButton;
    NumEdit: TEdit;
    ResultsMemo: TMemo;
    procedure SpawnButtonClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  PrimeFrm: TPrimeFrm;

implementation

uses PrimeThread;

{$R *.dfm}

procedure TPrimeFrm.SpawnButtonClick(Sender: TObject);
var
  NewThread: TPrimeThrd;

begin
  ResultsMemo.Lines.Add('Calculation began...');

  NewThread := TPrimeThrd.Create(True);
  NewThread.FreeOnTerminate := True;
  try
    NewThread.TestNumber := StrToInt(Trim(NumEdit.Text));
    ResetEvent(hEvent);
    NewThread.Resume;  
  except
    on EConvertError do
    begin
      NewThread.Free;
      ShowMessage('That is not a valid number!');
    end;
  end;


  case WaitForSingleObject(hEvent, INFINITE) of   //***PROBLEM
    WAIT_TIMEOUT : ResultsMemo.Lines.Add('It takes too long');
    WAIT_OBJECT_0 : ResultsMemo.Lines.Add('OK');
    WAIT_FAILED : ResultsMemo.Lines.Add('Failed');
  end;

  //***it NEVER returns the value, looks like SetEvent(hEvent) was never executed- but it was in UpdateResults!

end;

end.


xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

unit PrimeThread;

interface

uses
  Classes, Windows;

type
  TPrimeThrd = class(TThread)
  private
    FTestNumber: integer;
    FResultString: string;
  protected
    function IsPrime: boolean;
    procedure Execute; override;
    procedure UpdateResults;
  public
    property TestNumber: integer write FTestNumber;
  end;

var
  hEvent : THandle;
implementation

uses SysUtils, Dialogs, PrimeForm;

function TPrimeThrd.IsPrime: boolean;
var iter: integer;
begin
  result := true;
  if FTestNumber < 0 then
  begin
    result := false;
    exit;
  end;
  if FTestNumber <= 2 then
    exit;
  for iter := 2 to FTestNumber - 1 do
  begin
    if (FTestNumber mod iter) = 0 then
    begin
      result := false;
      {exit;}
    end;
  end;
end;

procedure TPrimeThrd.Execute;
begin
  if IsPrime then
    FResultString := IntToStr(FTestNumber) + ' is prime.'
  else
    FResultString := IntToStr(FTestNumber) + ' is not prime.';
  Synchronize(UpdateResults);

end;

procedure TPrimeThrd.UpdateResults;
begin
  PrimeFrm.ResultsMemo.Lines.Add(FResultString);
  SetEvent(hEvent);  
end;

initialization
  hEvent := CreateEvent(nil, True, false, PChar('MCS_WFSO_ExampleEvent'));

finalization
  CloseHandle(hEvent);

end.
ActiaAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

robert_marquardtCommented:
This cannot work because the WaitForSingleObject blocks the main thread until the signal occurs.
Synchronize in the TPrimeThrd blocks because the main thread is blocked and cannot handle messages. Synchonization relies on messages.

All in all you are completely off-track.
If you block the main thread then there is no use for a secondary thread becasue the main thread could do it instead.
What you should do is create a Delphi event in TPrimeFrm (something like OnThreadUpdate) and the implementation is in TPrimeThrd.UpdateResults.
0
ActiaAuthor Commented:
>>This cannot work because the WaitForSingleObject blocks the main thread until the signal occurs.
Synchronize in the TPrimeThrd blocks because the main thread is blocked and cannot handle messages. Synchonization relies on messages.

Right...!


>>All in all you are completely off-track.
If you block the main thread then there is no use for a secondary thread becasue the main thread could do it instead.

I know- I wrote this software just to learn about Threads.

What I've done to make it work is:

procedure TPrimeThrd.Execute;
begin
  if IsPrime then
    FResultString := IntToStr(FTestNumber) + ' is prime.'
  else
    FResultString := IntToStr(FTestNumber) + ' is not prime.';

  SetEvent(hEvent);                //*** put this line here
  Synchronize(UpdateResults); //instead of inside Synchronize

end;

and now it works fine, but maybe I can do it even better?

Jack




0
jpedefCommented:
Maybe you should take a look at http://www.midnightbeach.com/jon/pubs/MsgWaits/MsgWaits.html
There is few quite nice thread implementations that might be helpful in your case.
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
Cloud Class® Course: Microsoft Exchange Server

The MCTS: Microsoft Exchange Server 2010 certification validates your skills in supporting the maintenance and administration of the Exchange servers in an enterprise environment. Learn everything you need to know with this course.

robert_marquardtCommented:
The basic idea to block the main thread is simply wrong.
Your change is not really correct either. Now the main thread outputs first (or at least can output first) so for a real world example the result from the thread is not yet available the instant the event is received.
0
ActiaAuthor Commented:
>The basic idea to block the main thread is simply wrong.
Your change is not really correct either. Now the main thread outputs first (or at least can output first) so for a real world example the result from the thread is not yet available the instant the event is received.

Right... maybe I will explain what I exactly want to achieve. My application is supposed to send messages to external electronic unit. It is 2 way communication like this one:

1. My application: send a command
2. My application WAIT for max 3 seconds to get a reply (in the code I provided I replaced electronic unit with prime number calculation)
3. After getting the reply -> send next command
3.1 Wrong or not reply -> raise an exception and do not send next command

So... are you saying that my approach is simple wrong? If so- which one is better?

Jack
0
robert_marquardtCommented:
I would set up a complete class for that.
The API would be a SendCommand method and a OnCommandCompleted event.
The event would have a reason parameter for the completion like success, failure, timeout and of course any data returned.
Sending the command and waiting with timeout is done in a thread which is kept locally in the class.

You may need to implement a command queue for the thread to work on. That is SendCommand places the command in a queue to work on and starts the worker thread if it is not already running.
SendCommand then returns to the main thread which can call SendCommand before the command has been completed. So the new command is placed in the queue to serialize the commands.
The thread works until the queue is empty and then terminates. The next Sendcommand will start a new worker thread.

I assume that you write the command with WriteFileEx overlapped and then wait with WaitForSingleObject 8with 3 sec timeout) for the completion of the call inside the thread.
This is what the thread does. Take a command out of the queue, do the write, wait for its completion, synchronize the OnCommandCompleted event. This is repeated until the command queue is empty.
You will have to use a mutex to guard the access to the queue so you get no synchronization problems between the main thread enqueueing commands and the thread dequeueing them.
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Delphi

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.