1

I have a child from TThread. Everything works fine, but how can I do a mass suspend or resume of my created threads? Or how can I suspend only a second thread (which was created in Button2Click)? Here is a part of my code:

TMyThread = class(TThread)
private
  source_file, destination_file: string;
  total_size, current_size, download_item_id: integer;
protected
  procedure ShowResult;
  procedure Execute; override;
public
end;

var
 MyThread: TMyThread;

begin

procedure TMyThread.Execute;
 begin
  //Some code for download file here, it doesn't matter
 end;


procedure TForm1.Button1Click(Sender: TObject);
begin
  MyThread := TMyThread.Create(True);
  MyThread.source_file :='http://example.com/download1.zip';
  MyThread.destination_file := 'c:\download1.zip';
  MyThread.download_item_id := 0;
  MyThread.Priority := tpNormal;
  MyThread.FreeOnTerminate := True;
  MyThread.Resume;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  MyThread := TMyThread.Create(True);
  MyThread.source_file :='http://example.com/download2.zip';
  MyThread.destination_file := 'c:\download2.zip';
  MyThread.download_item_id := 1;
  MyThread.Priority := tpNormal;
  MyThread.FreeOnTerminate := True;
  MyThread.Resume;
end;

end.

That is, if I create a threads like this - it's works for me:

var
 MyThread1, MyThread2: TMyThread;
...
procedure TForm1.Button1Click(Sender: TObject);
begin
  MyThread1 := TMyThread.Create(True);
  MyThread1.source_file :='http://example.com/download1.zip';
  MyThread1.destination_file := 'c:\download1.zip';
  MyThread1.download_item_id := 0;
  MyThread1.Priority := tpNormal;
  MyThread1.FreeOnTerminate := True;
  MyThread1.Resume;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  MyThread2 := TMyThread.Create(True);
  MyThread2.source_file :='http://example.com/download2.zip';
  MyThread2.destination_file := 'c:\download2.zip';
  MyThread2.download_item_id := 1;
  MyThread2.Priority := tpNormal;
  MyThread2.FreeOnTerminate := True;
  MyThread2.Resume;
end;

//Terminate all of TMyThread
procedure TForm1.Button3Click(Sender: TObject);
begin
  MyThread1.Terminate;
  MyThread2.Terminate;
  ShowMessage('All downloads were terminated!');
end;

//Terminate ONLY the second of TMyThread
procedure TForm1.Button4Click(Sender: TObject);
begin
  MyThread2.Terminate;
  ShowMessage('The second download was terminated!');
end;

But how to do it for a set of dynamically created TMyThread (as in the first code sample)?

Red October
  • 689
  • 2
  • 12
  • 31
  • 3
    Use [`TThread.Start`](http://docwiki.embarcadero.com/Libraries/XE3/en/System.Classes.TThread.Start) instead of `Resume`. The warning message that `Resume` is being deprecated must said that. Also don't access `private` members of a class, that's why they're private. And, don't try to *pause* thread by `Suspend` or something. If you seriously need that, use a system object, e.g. event, and wait for it e.g. by using `WaitForSingleObject`. – TLama Dec 16 '12 at 09:35
  • Thanks for your comments, but the question is: how to access to dynamically created threads? I mean, it can be a mass `TThread.Terminate`, not necessarily exactly `TThread.Suspend` or `TThread.Resume`. – Red October Dec 16 '12 at 09:43
  • 1
    See [With what delphi Code should I replace my calls to deprecated TThread method Suspend?](http://stackoverflow.com/q/2097316/576719). An Event is a waitable object. If you have one global event for your threads they can all wait for that. If you need to control your threads separately, pass them an individual event object in a custom `TThread.Create`. – LU RD Dec 16 '12 at 09:50
  • Thanks, but this is not quite what I need. I edited the question and added another one example: maybe will become more clear what I meant. – Red October Dec 16 '12 at 10:05
  • 1
    You access them a some single threads. You have to store the references. If you don't know how much you should use a list – Sir Rufo Dec 16 '12 at 10:06
  • Very well, suppose I have a list of all the IDs (something like 4423, 5109 etc) from already created TThread(s), but how can I refer to the thread by its ID? – Red October Dec 16 '12 at 10:18
  • You are setting `FreeOnTerminate` to true. This means you cannot safely refer to your thread instances. Pass them an event that you control. – LU RD Dec 16 '12 at 10:19
  • That sounds like a task for `TDictionary`, where the key will be the ID you've specified. – TLama Dec 16 '12 at 10:21
  • @TLama, yes but in this case the threads can already be terminated by themself. Better to keep a dictionary of events in this case. – LU RD Dec 16 '12 at 10:23
  • @TLama, to keep the IDs of threads is not a problem, but how refer to them? Something like `TerminateThread (my_thread_id, 0);` (btw, it does not work to me). – Red October Dec 16 '12 at 10:27
  • @LURD, you're right, I missed that free at termination! However, even storing events to the collection won't help too much, since then you can have an endless collection of garbage events because the collection don't know when the thread has been terminated (and so destroyed). There should be added yet some mechanism that will remove the item from the collection when the thread is terminated (I'd think of from the `DoTerminate` event post some destroy notify message to the form with that unique thread ID). – TLama Dec 16 '12 at 10:58
  • @TLama, correct, notify the form in the DoTerminate event, so the information linked with the ID is destroyed. – LU RD Dec 16 '12 at 11:04

2 Answers2

8

Limitation

You really, really shouldn't keep references to threads that are set to free on termination. It is asking for all sorts of problems. The code below does use threads with FreeOnTerminate set to True. This is only safe because: 1) the references are removed as the thread terminates and before it is freed; 2) The OnTerminate handler is called in the context of the mainthread and/or a thread safe TList descendant is used; and - most importantly - 3) the references are only used to cancel threads selectively from within the context of the main thread.

As soon as you want to use the references for anything else (like pausing and resuming) or could access them from within the context of any thread other than the main thread, you really should delve a lot deeper into various mechanisms of threaded program execution.

A couple of keywords: thread synchronization, event signalling, mutex's, semaphores, critical sections.

A good reference to multi-threaded programming with Delphi is Multithreading - The Delphi Way

Answer

If you really need to reference the threads later on, you do need to store your references somewhere. But you do not need to have separate variables for each thread you create. There are many options. An array comes to mind, but probably the simplest is an object list (from the Contnrs unit):

TForm1 = class(TForm)
  private
    MyThreads: TObjectList;

constructor TForm1.Create(AOwner: TComponent);
begin
  inherited;
  MyThreads := TObjectList.Create;
end;

destructor TForm1.Destroy;
begin
  MyThreads.Free;
  inherited;
end;

TObjectList has an OwnsObjects property which by default is True. This means that freeing the object list will also free the instances it contains. And this means that you need to ensure that the list only contains valid references. Normally that is not a problem, but with threads you need to take special care, especially when you use FreeOnTerminate set to True.

Create your threads like you do in your first example, with a couple of changes:

Use a local variable (you should avoid global variables, even if they are only visible within the unit as much as possible).

Add the thread to your object list.

Set an OnTerminate handler to remove the instance from your object list when you also want to use 'FreeOnTerminate` set to True.

procedure TForm1.Button1Click(Sender: TObject);
var
  Thread: TThread;                                       // Local var
begin
  Thread := TMyThread.Create(True);
  MyThreads.Add(Thread);                                 // Add to list
  Thread.source_file :='http://example.com/download1.zip';
  Thread.destination_file := 'c:\download1.zip';
  Thread.download_item_id := 0;
  Thread.Priority := tpNormal;
  Thread.OnTerminate := HandleThreadTerminate;           // Ensure you keep list valid
  Thread.FreeOnTerminate := True;                        // Advice against this!
  Thread.Start;
end;

procedure TForm1.HandleThreadTerminate(Sender: TObject);
var
  idx: Integer;
begin
  // Acquire Lock to protect list access from two threads
  idx := MyThreads.IndexOf(Sender);
  if idx > -1 then
    MyThreads.Delete(idx);
  // Release lock
end;

The HandleThreadTerminate procedure should use some kind of lock, so two threads cannot at the same time try to delete instances from the list as a thread switch between IndexOf and Delete could mean deleting the wrong instance. The code in the handler could be written as MyThreads.Remove(Sender). Although that is a single statement, it isn't thread safe as it does the same as the code I've shown behind the scenes.

Edit: Actually, as @LURD mentions it happens to be thread safe because OnTerminate is called in the context of the main thread. I am leaving the TThreadList example because it is a good way to do things if you need / happen to access the list from multiple threads.

To have the locking stuff taken care of automatically, you can use a TThreadList (from the classes unit). Required code changes:

Of course you need to instantiate a TThreadList instead of a TObjectList.

constructor TForm1.Create(AOwner: TComponent);
begin
  inherited;
  MyThreads := TThreadList.Create;
end;

Because TThreadList has no concept of OwnsObjects, you will have to free any remaining threads yourself:

destructor TForm1.Destroy;
var
  LockedList: TList;
  idx: integer;
begin
  LockedList := MyThreads.LockList;
  try
    for idx := LockedList.Count - 1 downto 0 do
      TObject(MyThreads.Items(idx)).Free;
  finally
    MyThreads.UnlockList;
  end;

  MyThreads.Free;
  inherited;
end;

The OnTerminate handler can now safely be simplified as the TThreadList will take care of the required locking.

procedure TForm1.HandleThreadTerminate(Sender: TObject);
begin
  MyThreads.Remove(idx);
end;

Edit: As @mghie mentions, freeing a thread when FreeOnTerminate is true clashes with the whole idea of free on terminate. You can either set FreeOnTerminate to false before you free the thread (which would terminate it), or if you want to keep the FreeOnTerminate handling, you should disconnect the OnTerminate handler and then use Terminate to tell the thread to terminate.

destructor TForm1.Destroy;
var
  LockedList: TList;
  idx: integer;
begin
  LockedList := MyThreads.LockList;
  try
    for idx := LockedList.Count - 1 downto 0 do
    begin
      TThread(MyThreads.Items(idx)).OnTerminate := nil;
      TThread(MyThreads.Items(idx)).Terminate;
    end;
  finally
    MyThreads.UnlockList;
  end;

  MyThreads.Free;
  inherited;
end;
Marjan Venema
  • 19,136
  • 6
  • 65
  • 79
  • The `OnTerminate` event is executed in the context of the main thread. No need to protect the list. – LU RD Dec 16 '12 at 11:52
  • 1
    Never call `Free` on a thread that has `FreeOnTerminate` set. That can't be done safely without further synchronization, which clashes with the whole idea of `FreeOnTerminate`. – mghie Dec 16 '12 at 11:59
  • @LURD: D'oh. Indeed. Added a note about that. – Marjan Venema Dec 16 '12 at 12:00
  • @mghie: On second thought I agree, added note. – Marjan Venema Dec 16 '12 at 12:11
  • If you are using FreeOnTerminate, never hold a reference to the thread. Not one. If FreeOnTerminate is False then you can hold references. – David Heffernan Dec 16 '12 at 12:21
  • @DavidHeffernan: While I agree with your advice as a general rule, I don't immediately see a problem when you ensure the references are deleted before the thread actually is "Freed on termination"? – Marjan Venema Dec 16 '12 at 12:24
  • No problem if you do it right. But why bother with FreeOnTerminate if you are holding a list of them? FreeOnTerminate is for when you want to forget the reference. – David Heffernan Dec 16 '12 at 12:30
  • @mghie, this exactly what I need! Thank you and thanks to all who participated in the discussion! – Red October Dec 16 '12 at 12:33
  • 2
    Also, where is the discussion of pausing? I trust @RedOctober doesn't plan to use the Suspend method. My point is that maintaining a list of threads is not helpful. To perform safe pause, resume, cancel you need a token that is checked by all threads from their thread proc. – David Heffernan Dec 16 '12 at 12:34
  • @DavidHeffernan: If all you want is the ability to selectively cancel a specific thread's execution - and that seems to be what OP is asking, then calling terminate from the main thread should be all you need and is perfectly safe if you ensure that the reference does not extend beyond the thread's lifetime. If you want more control than that and/or would be accessing the references from anything but the main thread, yes, you absolutely need (are better off with) signal events or other tokens. – Marjan Venema Dec 16 '12 at 13:56
  • In older versions of Delphi, the terminate part of a thread used global instances of the Classes.pas library which are being destroyed during application shutdown. This could cause a 'FreeOnTerminate'-Thread which is still running during application shutdown to crash and generate an ugly AV. So I usually avoid FreeOnTerminate-Threads and terminate and free them myself. – alzaimar Dec 16 '12 at 22:44
1

The answer is really simple - if you create a FreeOnTerminate type TThread then you do not utilize any outside references at all to that TThread instance. To do so is like playing Russian Roulette...it will blow up in your face sooner or later. Yes, there are some ways to do it, but you just shouldn't.

What you can do is to introduce a shared communication medium between the parent and child. There are many different ways of communicating status updates from the child thread that do not involve any direct reference to the TThread instance. (Check out custom windows messages, the likely most used method.)

A really obvious (I think), simplistic example. I am coding this inside this message box so don't expect it to compile... This is just to help demonstrate that a separate mechanism is required that needs to be configured so you can safely communicate between the parent and child without relying on a reference to some TThread instance. (This assumes one child thread...and yes, there are many other ways of doing this.)

unit MyPubComm.pas;

initialization
uses ...;

type
  TThreadComm = class
  private
    fThreadIsBusy:Boolean;  //more likely a State of some sort
    fThreadMessage:String;
  public
   property ThreadIsBusy:Boolean read fThreadIsBusy write fThreadIsBusy;
   property ThreadMessage:String read fThreadMessage write fThreadMessage;
  end;

  function IsChildBusy:Boolean;
  function ChildMessage:String;
  procedure SetThreadMessage(const MessageText:String);

var
  pubThreadStatus:TThreadComm;


implementation

function IsChildBusy:Boolean;
begin
  pubThreadStatus.Monitor.Enter;
  try
    Result := pubThreadStatus.ThreadIsBusy;
  finally
    pubThreadStatus.Monitor.Exit;
  end;
end;

function ChildMessage:String;
begin
  pubThreadStatus.Monitor.Enter;
  try
    Result := pubThreadStatus.ThreadMessage;
  finally
    pubThreadStatus.Monitor.Exit;
  end;
end;

procedure SetThreadMessage(const MessageText:String);
begin
  pubThreadStatus.Monitor.Enter;
  try
    pubThreadStatus.ThreadMessage := MessageText;
  finally
    pubThreadStatus.Monitor.Exit;
  end;
end;


initialization
  pubThreadStatus := TThreadCom.Create;
finalization
  pubThreadStatus.Free();

--------
unit MainAppCode.pas
...
implementation
Uses MyPubComm;

procedure TMyForm1.WorkingOnSomething();
begin
   if IsChildBusy() then  //safely check child state
   begin 
      label1.caption := 'Child thread busy...';
   end
   else
   begin
      label1.caption := ChildMessage();  //safely retrieve shared data
   end;
end;
------
MyThread.pas
...
implementation
Uses MyPubComm;

function TMyThread.WorkerBee();
begin
   If TimeToCommunicateThreadMessage then
   begin 
     SetThreadMessage(MyMessage);  //safely update shared data
   end;
end;

This also should show an obvious problem in WorkingOnSomething() Between the time of the check IsChildBusy() and the ChildMessage() call, the actual busy state and message text could change which is why a much more robust communication method is required.

Darian Miller
  • 7,808
  • 3
  • 43
  • 62