Link to home
Create AccountLog in
Avatar of Actia
Actia

asked on

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.
Avatar of robert_marquardt
robert_marquardt

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.
Avatar of Actia

ASKER

>>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




ASKER CERTIFIED SOLUTION
Avatar of jpedef
jpedef
Flag of Finland image

Link to home
membership
Create a free account to see this answer
Signing up is free and takes 30 seconds. No credit card required.
See answer
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.
Avatar of Actia

ASKER

>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
SOLUTION
Link to home
membership
Create a free account to see this answer
Signing up is free and takes 30 seconds. No credit card required.