Question

New here. Relatively new to Delphi as well so plz be kind...

My actual (domain) problem: small VCL app that communicates with two laboratory balances via serial, balances output weight readings on a continuous 1-second interval, said weighs are displayed in the captions of two labels. When user clicks a 'Weigh' button, I need to wait for a valid weight (stable, within range, etc.) and record said weight once, -or-, allow the user to cancel the weighing.

My problem as implemented. Creating a separate thread when user clicks weigh button using TEvent template from S.MAHDI / David Heffernan as shown in this post.

TWeigh = class(TThread)
  private
    FTerminateEvent: TEvent;
  protected
    procedure Execute; override;
    procedure TerminatedSet; override;
  public
    constructor Create(ACreateSuspended: Boolean);
    destructor Destroy; override;
end;

constructor TWeigh.Create(ACreateSuspended: Boolean);
begin
  FTerminateEvent := TEvent.Create(nil, True, False, '');
  inherited;
end;

destructor TWeigh.Destroy;
begin
  inherited;
  FTerminateEvent.Free;
end;

procedure TWeigh.TerminatedSet;
begin
  FTerminateEvent.SetEvent;
  Beep;
end;

procedure TWeigh.Execute();
begin
  while (not Terminated) do begin
    if (validweight) then begin
      Synchronize(procedure begin
        DoStuff();
      end);
    end;
    FTerminateEvent.WaitFor(100);
  end;
end;

My form button click event looks like:

{ Weigh is global implementation var of class TWeigh }
procedure TForm1.btnWeighClick(Sender: TObject);
var
  B : TButton;
begin
  B := Sender as TButton;
  if (B.Caption = 'Weigh') then Weigh := TWeigh.Create(False);
  if (B.Caption = 'Cancel') then Weigh.Free;
  B.Caption := Trim(Copy('CancelWeigh ',AnsiPos(B.Caption,'Weigh Cancel'),6));
end;

This appears to work fine for the cancellation requirement. It's my understanding that Synchronize messages the main thread and it is the main thread that executes the anonymous procedure containing DoStuff(), so there should be no race conditions between the user clicking cancel and a valid weight coming in. (Right?)

I'm stuck on how to have the weigh thread only execute a single time. Various solutions I've tried have resulted in deadlocks (add Weigh.Free to DoStuff()... didn't take long to figure out why THAT doesn't work), single execution but non-free'd threads (self.Terminate after the synchronize section within the if block), or various other nonsense.

So, is it even possible to have this thread free and/or kill itself while still allowing for the parent thread via user input to kill it, or do I need a completely different architecture for this?

Edit in response to why a loop: I only need a single reading, but the time until I get that single reading varies between immediately and never. The balances can take several seconds to stabilize, during which time unstable readings are read and displayed every second. The ability for the user to cancel is still required because the reading might never be valid (under-overweight).

Was it helpful?

Solution

If I understand correctly, you wish to quit the thread when you've finished calling DoStuff. That can be done like so:

procedure TWeigh.Execute();
begin
  while (not Terminated) do begin
    if (validweight) then begin
      Synchronize(procedure begin
        DoStuff();
      end);
      exit;
    end;
    FTerminateEvent.WaitFor(100);
  end;
end;

I have to say that this looks more appropriate for a timer than a thread. All the work is done on the main thread, and the thread just appears to be there to check a flag at a regular interval. That sounds like a timer. In fact, why even a timer? Why not fire the DoStuff when you set the flag true?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top