How do I reliably wait on a thread that has just been created?
Asked Answered
P

4

10

Consider the following program:

program TThreadBug;
{$APPTYPE CONSOLE}

uses
  SysUtils, Classes, Windows;

type
  TMyThread = class(TThread)
  protected
    procedure Execute; override;
  end;

procedure TMyThread.Execute;
var
  i: Integer;
begin
  for i := 1 to 5 do begin
    Writeln(i);
    Sleep(100);
  end;
end;

procedure UseTThread;
var
  Thread: TMyThread;
begin
  Writeln('TThread');
  Thread := TMyThread.Create;
  Thread.Terminate;
  Thread.WaitFor;
  Thread.Free;
  Writeln('Finished');
  Writeln;
end;

procedure UseTThreadWithSleep;
var
  Thread: TMyThread;
begin
  Writeln('TThreadWithSleep');
  Thread := TMyThread.Create;
  Sleep(100);
  Thread.Terminate;
  Thread.WaitFor;
  Thread.Free;
  Writeln('Finished');
  Writeln;
end;

begin
  UseTThread;
  UseTThreadWithSleep;
  Readln;
end.

The output is:

TThread
Finished

TThreadWithSleep
1
2
3
4
5
Finished

So it seems that, for some reason, the main thread has to wait some arbitrary amount of time before terminating and waiting for the worker thread. Am I right in thinking that this is a bug in TThread? Is there any way I can work around this? I expect that if I get my thread to signal that it has started (using an event), then that would work around the issue. But that makes me feel dirty.

Pennypennyaliner answered 23/2, 2013 at 15:15 Comment(3)
The reason is clearly that ThreadProc in Classes does if not Thread.Terminated then try Thread.Execute;, at least in Delphi 2009.Viviyan
@David, I don't get the idea. If you create the thread and wait for it in the next line, it's not better to run the code on the same thread? what's the benefit of running it on a different thread?Alvord
@Alvord In the real code there are multiple consumer threads. There are also times when it's useful to run code off the current thread. One example I have encountered is code that leads to a message queue being attached to a thread. If I don't want a queue I can run the code off thread and then discard that thread.Pennypennyaliner
T
16

You can call it a bug or a TThread design flaw, the problem was discussed many times. See for example http://sergworks.wordpress.com/2011/06/25/sleep-sort-and-tthread-corner-case/

The problem is that if TThread.Terminated flag is set too early TThread.Execute method is never called. So in your case just don't call TThread.Terminate before TThread.WaitFor.

Tingley answered 23/2, 2013 at 15:22 Comment(13)
Thank you. Your link explains it. Now I'll have to think of a workaround for my real code.Pennypennyaliner
Why would you desire the thread to run if you call terminate on it? That is, the thread does run anyway, it only doesn't always call Execute if you terminate it before it would.Aran
@Aran - because from the programmer's point of view 'thread does run' = 'thread.execute was called'.Tingley
Anyone knows why this is done this way? It's sort of breaking a contract. To circumvent, I usually implement a flag in the thread set to true first thing in Execute.Recipient
@Serg Sure. Just wondering about a use case. Normally I terminate a thread because I close the application or cancel a certain process. If my desire is to cancel an operation, I couldn't care less if it was already started or not. Just curious about the case where you already know you want to cancel it, but still want it to start first.Aran
@Aran - The use case is to make sure that Execute was executed. :) I guess you can find a simple workaround in every case, like not calling Terminate before WaitFor in the OP code. It is just a hidden and unpleasant feature you should be aware of.Tingley
@Golez Here's my use case. The thread is a consumer that pulls tasks off a threadsafe queue. I load up the queue. Then create the thread passing in the queue. Then I call Free on the thread. The design of the thread's Execute method is that it must empty the queue before returning. But the thread's execute method never runs.Pennypennyaliner
I consider this to be a gross design flaw. It's my thread and I should decide when to respond to termination.Pennypennyaliner
Can't the thread just free itself when the queue is empty? You can use the FreeOnTerminate property and the OnTerminate event to hold a grip on that. The thread can automatically free itself using that property, and can signal your main thread using the event.Aran
@Golez That's not what I want. i want to block until consumer is done.Pennypennyaliner
Block what? The main thread? You can use WaitFor for that, or a simple counter that keeps track of running threads. I just mean to say that you don't need to call Terminate for your threads to stop. You can just run the thread and call WaitFor, which will wait until the thread is terminated i.e. until the Execute method is finished.Aran
If you want to wait for a single thread, like in your code, then there's no use at all for a separate thread.Aran
@DavidHeffernan That's why OTL contains IOmniTaskControl.Enforced, which forces task to be executed even if Terminate is called before the task is scheduled.Fokker
A
5

I think the reason why this happens, has been sufficiently answered by Serg's answer, but I think you should not normally call Thread.Terminate anyway. The only reason to call it, if you want the thread to terminate, for instance when the application is closing. If you just want to wait until it is finished, you can call WaitFor (or WaitForSingleObject). This is possible, because the handle for the thread is already created in its constructor, so you can call it right away.

Also, I set FreeOnTerminate to true on these threads. Just let them run and free themselves. If I want a notification of them to be done, I can use either WaitFor or the OnTerminate event.

Here's just an example of a bunch of worker threads emptying a queue in a blocking way.

I would think you shouldn't need this, David, but maybe someone else may be happy with an example. On the other hand, you probably didn't ask this question just to have a change to rant about TThread's poor implementation, right? ;-)

First the Queue class. It's not really a traditional queue, I think. In a real multi-threaded queue, you should be able to add to the queue at any point, even when the processing is active. This queue requires you to fill its items upfront, then call the -blocking- run method. Also, the processed items are saved back to the queue.

type
  TQueue = class
  strict private
    FNextItem: Integer;
    FRunningThreads: Integer;
    FLock: TCriticalSection;
    FItems: TStrings; // Property...
  private

    // Signal from the thread that it is started or stopped.
    // Used just for indication, no real functionality depends on this.
    procedure ThreadStarted;
    procedure ThreadEnded;

    // Pull the next item from the queue.
    function Pull(out Item: Integer; out Value: string): Boolean;

    // Save the modified value back in the queue.
    procedure Save(Item: Integer; Value: string);

  public
    property Items: TStrings read FItems;
    constructor Create;
    destructor Destroy; override;

    // Process the queue. Blocking: Doesn't return until every item in the
    // queue is processed.
    procedure Run(ThreadCount: Integer);

    // Statistics for polling.
    property Item: Integer read FNextItem;
    property RunningThreads: Integer read FRunningThreads;
  end;

Then the Consumer thread. That one is plain and easy. It just has a reference to the queue, and an execute method that runs until the queue is empty.

  TConsumer = class(TThread)
  strict private
    FQueue: TQueue;
  protected
    procedure Execute; override;
  public
    constructor Create(AQueue: TQueue);
  end;

Here you see the implementation of this obscure 'Queue'. It's main methods are Pull and Save, which are used by the Consumer to pull the next item, and save the processed value back.

Another important method is Run, which starts a given number of worker threads and waits until all of them are finished. So this is actually a blocking method, which only returns after the queue is emptied. I'm using WaitForMultipleObjects here, which allows you to wait for upto 64 threads before you need to add extra tricks. It's the same as using WaitForSingleObject in the code in your question.

See how Thread.Terminate is never called?

{ TQueue }

constructor TQueue.Create;
// Context: Main thread
begin
  FItems := TStringList.Create;
  FLock := TCriticalSection.Create;
end;

destructor TQueue.Destroy;
// Context: Main thread
begin
  FLock.Free;
  FItems.Free;
  inherited;
end;

function TQueue.Pull(out Item: Integer; out Value: string): Boolean;
// Context: Consumer thread
begin
  FLock.Acquire;
  try
    Result := FNextItem < FItems.Count;
    if Result then
    begin
      Item := FNextItem;
      Inc(FNextItem);
      Value := FItems[Item];
    end;
  finally
    FLock.Release;
  end;
end;

procedure TQueue.Save(Item: Integer; Value: string);
// Context: Consumer thread
begin
  FLock.Acquire;
  try
    FItems[Item] := Value;
  finally
    FLock.Release;
  end;
end;

procedure TQueue.Run(ThreadCount: Integer);
// Context: Calling thread (TQueueBackgroundThread, or can be main thread)
var
  i: Integer;
  Threads: TWOHandleArray;
begin
  if ThreadCount <= 0 then
    raise Exception.Create('You no make sense no');
  if ThreadCount > MAXIMUM_WAIT_OBJECTS then
    raise Exception.CreateFmt('Max number of threads: %d', [MAXIMUM_WAIT_OBJECTS]);

  for i := 0 to ThreadCount - 1 do
    Threads[i] := TConsumer.Create(Self).Handle;

  WaitForMultipleObjects(ThreadCount, @Threads, True, INFINITE);
end;

procedure TQueue.ThreadEnded;
begin
  InterlockedDecrement(FRunningThreads);
end;

procedure TQueue.ThreadStarted;
begin
  InterlockedIncrement(FRunningThreads);
end;

The code for the consumer thread is plain and easy. It signals its start and end, but that's just cosmetic, because I want to be able to show the number of running threads, which is at it's max as soon as all threads are created, and only starts declining after the first thread exits (that is, when the last batch of items from the queue are being processed).

{ TConsumer }

constructor TConsumer.Create(AQueue: TQueue);
// Context: calling thread.
begin
  inherited Create(False);
  FQueue := AQueue;
  // A consumer thread frees itself when the queue is emptied.
  FreeOnTerminate := True;
end;

procedure TConsumer.Execute;
// Context: This consumer thread
var
  Item: Integer;
  Value: String;
begin
  inherited;

  // Signal the queue (optional).
  FQueue.ThreadStarted;

  // Work until queue is empty (Pull returns false).
  while FQueue.Pull(Item, Value) do
  begin
    // Processing can take from .5 upto 1 second.
    Value := ReverseString(Value);
    Sleep(Random(500) + 1000);

    // Just save modified value back in queue.
    FQueue.Save(Item, Value);
  end;

  // Signal the queue (optional).
  FQueue.ThreadEnded;
end;

Of course, if you want to view the progress (or at least a little), you don't want a blocking Run method. Or, like I did, you can execute that blocking method in a separate thread:

  TQueueBackgroundThread = class(TThread)
  strict private
    FQueue: TQueue;
    FThreadCount: Integer;
  protected
    procedure Execute; override;
  public
    constructor Create(AQueue: TQueue; AThreadCount: Integer);
  end;

    { TQueueBackgroundThread }

constructor TQueueBackgroundThread.Create(AQueue: TQueue; AThreadCount: Integer);
begin
  inherited Create(False);
  FreeOnTerminate := True;
  FQueue := AQueue;
  FThreadCount := AThreadCount;
end;

procedure TQueueBackgroundThread.Execute;
// Context: This thread (TQueueBackgroundThread)
begin
  FQueue.Run(FThreadCount);
end;

Now, calling this from the GUI itself. I've created a form, that holds two progress bars, two memo's, a timer and a button. Memo1 is filled with random strings. Memo2 will receive the processed strings after processing is fully done. The timer is used to update the progress bars, and the button is the only thing that actually does something.

So, the form just contains all these fields, and a reference to the queue. It also contains an event handler to be notified when processing is complete:

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    Memo2: TMemo;
    Timer1: TTimer;
    ProgressBar1: TProgressBar;
    ProgressBar2: TProgressBar;
    procedure Button1Click(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    Q: TQueue;
    procedure DoAllThreadsDone(Sender: TObject);
  end;

Button1 click event, initializes the GUI, creates the queue with 100 items, and starts a background thread to process the queue. This background thread receives an OnTerminate event handler (default property for TThread) to signal the GUI when processing is done.

You can just call Q.Run in the main thread, but then it will block your GUI. If that is what you want, then you don't need this thread at all!

procedure TForm1.Button1Click(Sender: TObject);
// Context: GUI thread
const
  ThreadCount = 10;
  StringCount = 100;
var
  i: Integer;
begin
  ProgressBar1.Max := ThreadCount;
  ProgressBar2.Max := StringCount;

  Memo1.Text := '';
  Memo2.Text := '';

  for i := 1 to StringCount do
    Memo1.Lines.Add(IntToHex(Random(MaxInt), 10));

  Q := TQueue.Create;
  Q.Items.Assign(Memo1.Lines);
  with TQueueBackgroundThread.Create(Q, ThreadCount) do
  begin
    OnTerminate := DoAllThreadsDone;
  end;
end;

The event handler for when the processing thread is done. If you want the processing to block the GUI, then you don't need this event handler and you can just copy this code to the end of Button1Click.

procedure TForm1.DoAllThreadsDone(Sender: TObject);
// Context: GUI thread
begin
  Memo2.Lines.Assign(Q.Items);
  FreeAndNil(Q);
  ProgressBar1.Position := 0;
  ProgressBar2.Position := 0;
end;

Timer is just for updating the progress bars. It fetches the number of running threads (which will only decline when processing is almost done), and it fetched the 'Item', which is actually the next item to process. So it may look finished already when actually the last 10 items are still being processed.

procedure TForm1.Timer1Timer(Sender: TObject);
// Context: GUI thread
begin
  if Assigned(Q) then
  begin
    ProgressBar1.Position := Q.RunningThreads;
    ProgressBar2.Position := Q.Item;
    Caption := Format('%d, %d', [Q.RunningThreads, Q.Item]);
  end;
  Timer1.Interval := 20;
end;
Aran answered 25/2, 2013 at 19:32 Comment(4)
My blocking queue and consumer thread code is here: #15028226Pennypennyaliner
Interesting setup, queueing procs as tasks. The actual dequeueing is very similar to what I built in a queue of my own (although that doesn't use generics, because it was meant to compile in D2007). But like I thought, you didn't really need a hand with that. ;)Aran
@DavidHeffernan, in a similar blocking queue and a consumer thread, I push PProc on the queue. When the consumer thread gets a nil, it's a signal to kill the consumer. The FreeOnTerminate is set to true, so either you can finish silently or signal as GolezTrol suggests. In a multiple consumer scheme, just push as many nils as there are consumers.Recipient
@LURD TProc can also be nil. In my production code I have multiple consumers and push one nil per consumer.Pennypennyaliner
H
2

I don't see this behavior as a bug in TThread. Execution of the new thread is supposed to occur independently of / asynchronous to the execution of the current thread. If things were set up such that the new thread was guaranteed to begin execution before TThread.Create() returns control to the caller in the current thread, that would mean execution of the new thread was (partially) synchronous to the current thread.

The new thread is added to the thread scheduling queue after the thread resources are allocated. If you're constructing a new thread from scratch (I seem to recall TThread does), this can take awhile because a lot of stuff has to be allocated behind the scenes. Avoiding this cost of getting a thread started is why ThreadPool.QueueUserWorkItem was created.

Furthermore, the behavior you're seeing fits perfectly well with the instructions you've laid out. Construct a new TThread. Immediately terminate it. Why is there any expectation that the new thread will have any opportunity to execute?

If you must have synchronous behavior around thread creation, you will at a minimum need to relinquish your remaining timeslice on the current thread. Sleep(0) will suffice. Sleep(0) gives up the rest of your current timeslice and immediately gets back into the scheduling queue behind whatever other threads (at your same priority) are waiting.

If you observe that Sleep(0) is not sufficient to getting the new thread up and running before the current thread calls Terminate, then thread creation overhead is probably preventing the new thread from getting into the thread-ready queue soon enough to satisfy your impatient current thread. In this case, try separating the overhead of thread construction from execution by constructing the new thread in the suspended state, then Start the new thread, then Sleep(0) in the current thread, then Terminate the new thread. This will give the new thread the best chance to get into the thread-ready schedule queue ahead of the current thread before the current thread terminates it.

This is as close as you're going to get to a "directed yield" in WinAPI without explicit cooperation or signaling from inside the new thread. Explicit cooperation / signalling from the new thread is the only way to guarantee that the calling thread will wait until after the new thread begins executing.

Signalling state between threads isn't dirty. What is dirty is expecting/requiring new thread construction to block the calling thread.

Hildy answered 1/3, 2013 at 22:4 Comment(3)
I most certainly don't expect the new thread construction to block the calling thread. All I want to do is be able to decided how to respond to Terminated being set to True. The design should be that the controlling thread requests termination by setting Terminated to True. And then the thread decides when to respond to that. But the design is other than that.Pennypennyaliner
Since responding to Terminated=true is voluntary by the implementation of the thread Execute method, you then run the risk that the controlling thread requests termination, but the targeted thread never terminates because it doesn't check the flag.Hildy
That's fine. That's as designed and the design is sound. Checking Terminated has to be at the discretion of the thread. So that it can tidy up properly. The problem is the inconsistency. If it's at the discretion of the thread then TThread should never check the flag. It should only ever be done by the thread's overriden Execute method.Pennypennyaliner
C
-1

As already explained you must wait for the thread until it has started before calling Terminate otherwise TThread.Execute will never be called. To do so you can wait until the property TThread.Started is true.

while not Thread.Started do;

Also you can call TThread.Yield while waiting for the thread to start, because this

notifies the system that it can pass the execution to the next scheduled thread on the current processor. The operating system will select the next thread.

while not Thread.Started do
  TThread.Yield;

At least we will end up with

procedure UseTThreadWithYield;
var
  Thread: TMyThread;
begin
  Writeln('TThreadWithYield');
  Thread := TMyThread.Create;

  // wait for the thread until started
  while not Thread.Started do
    TThread.Yield;

  Thread.Terminate;
  Thread.WaitFor;
  Thread.Free;
  Writeln('Finished');
  Writeln;
end;

and a generated output like this

TThreadWithYield
1
2
3
4
5
Finished
Custodian answered 17/9, 2014 at 12:26 Comment(1)
This is not correct. There's a data race on FStarted. It is set True before the test of Terminated. So, your while loop can terminate because Started evaluates True, and then you call Terminate. And then the code in Classes.ThreadProc checks the state of Terminated and finds that it is True and so does not call Execute.Pennypennyaliner

© 2022 - 2024 — McMap. All rights reserved.