1

I am looking for an ability to interrupt TThread quickly.

Some suggest to interrupt it with TerminateThread, but I do not want this violence.

I implemented an exit check in Execute method, but there is one thing I cannot influence: an ongoing Sleep. When a thread is in Sleep the only thing left to me is to wait until Sleep finishes.

I made a following workaround:

procedure TMyThreadTimer._smartSleep(Timeout: Integer);
var
  repeats: Integer;
begin
  repeats := (Timeout div 50) + 1;
  while (not Terminated) and (repeats > 0) do begin
    Sleep(50);
    Dec(repeats);
  end;
end;

but it looks not good.

Is there an ability to interrupt Sleep, but not the thread?

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
Paul
  • 25,812
  • 38
  • 124
  • 247

1 Answers1

11

Use a TEvent object instead of Sleep(), eg:

type
  TMyThreadTimer = class(TThread)
  private
    FTermEvent: TEvent;
    procedure _smartSleep(Timeout: Integer);
  protected
    procedure Execute; override;
    procedure TerminatedSet; override; // XE2+ only *
  public
    constructor Create(ACreateSuspended: Boolean); override;
    destructor Destroy; override;
  end;

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

destructor TMyThreadTimer.Destroy;
begin
  //inherited calls TerminatedSet where FTermEvent should not be freed yet
  inherited;
  FTermEvent.Free;
end;

procedure TMyThreadTimer.Execute;
begin
  ...
end;

procedure TMyThreadTimer.TerminatedSet;
begin
  FTermEvent.SetEvent;
end;

procedure TMyThreadTimer._smartSleep(Timeout: Integer);
begin
  FTermEvent.WaitFor(Timeout);
end;

This way, while the thread is running normally, _smartSleep() will sleep more efficiently, and as soon as you Terminate() the thread (as Terminate() calls TerminatedSet()), any sleep that is in progress will stop immediately.

* If you are using a Delphi version prior to XE2, you will have to implement your own method to signal the TEvent when needed. For example, adding a public Stop() method to the thread class and have it call Terminate() and FTermEvent.SetEvent(), then call that method instead of calling Terminate() directly.

Paul
  • 25,812
  • 38
  • 124
  • 247
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thank you. You are calling `inherited Create(ACreateSuspended);`, but I got problems when destroying a suspended thread. Destructor got stuck at `inherited`, so I decided never to allow suspended threads. – Paul Mar 17 '18 at 10:26
  • @Paul Presumably an initially-suspended thread would be started at some point. Why even create a thread just to destroy it before it does anything? Furthermore, the point of creating a thread in a suspended state is that you might need to do some additional work ***before*** it's ready to start running. If you start a thread before it's _ready_ You most ***certainly can expect*** peculiar, erratic, hard to fix bugs. Imposing a _poorly justified_ hard-nosed rule on client code just hampers the possibility of writing the client code correctly. – Disillusioned Mar 17 '18 at 14:01
  • 2
    The `TThread` destructor resumes a suspended thread and waits for it to terminate. But since `FTermEvent` is freed before the base destructor is called, bad things will happen when the thread is resumed. NEVER try to destroy a thread that has never started running, or is actively running. Always explicitly resume and terminate a thread before destroying it. – Remy Lebeau Mar 17 '18 at 15:37
  • 1
    Isn't the right way to deal with that to ensure that the terminate event is destroyed later? – David Heffernan Mar 19 '18 at 07:36