We help IT Professionals succeed at work.

How can I retrieve a list of child threads that belong to my application?

TheMatrixDuck
on
Medium Priority
399 Views
Last Modified: 2013-11-23
I would like to list and terminate selective threads that belong to my application.  Does anyone have any code for this?
Thanks!
Comment
Watch Question

developmentguruPresident

Commented:
The only, generic, way I can think of to do this would be to replace the memory manager with one you create (to keep track of all non-freed threads).  This would allow you to always have a list of threads.  Since a thread is a TObject descendant you can query the class name and use any of the TThread methods easily.  This would not handle any threads that are created with the windows API of course.

A non-generic way would be to create a TThread descendant that is the parent class for all of the threads you create.  This would allow you to keep a list, but only of the threads that your code creates.  Any threads created by, say, your UI controls would not be in the list.  This could be better depending on your needs.

Both methods assume that you are able to compile the application (as opposed to attaching to an application that you do not have the source code for).

So, I would need to know more about what you need to use it for.
  1) Generic for all TThreads in the application (your code or other code)?
  2) non-generic (your code only)?
  3) attached to a process that you cannot compile?

Author

Commented:
I'm not explicitly spawning threads, their being created with the SendMessage procedure from the main application thread.  I "think" I would have to grab all the threads for the application's process ID.  I know how to get the process ID, just don't know how to grab all the threads that belong to the process.

My application is throwing a runtime error during close.  I have stepped through the code and watched all components be freed and after the applications destructor completes, it throws the runtime error.  I suspect that there is a background thread running that's connected to the database, but I can't seem to find it after a week of looking.

Thanks.
Emmanuel PASQUIERFreelance Project Manager
CERTIFIED EXPERT
Top Expert 2010

Commented:
My application is throwing a runtime error during close
These are the most dreaded errors a Delphi developer could encounter. Some have eluded me for months before I could find a solution. It does not need threads to be in such kind of mess.
That is especially true if your software is using third party components or libraries, from which your application gets objects references, and there is not enough documentation to understand exactly what is the life-time of these objects.

A few cases :
a) you create objects dynamically that you attach to some VCL structure (ex. in treeview items data property), with some events code that will trigger when the VCL component is destroyed (automatically on form unload), while the objects have been destroyed elsewhere before. You will end up with exception from within deep VCL code, without a single function of your own code in the  call stack when the exception is raised. Good luck finding the real problem.
b) another kind of related issues would be events triggered or queued on  objects destroyed by the time the event code is treated/finished (ex. if you destroy a button in its own onClick event, it can work, but only because there is not much happening after .Click method is called - some more complex components might not like that at all)
c) you use COM interface or objects from third party, and by keeping a reference of it in your code you don't allow the object to be normally uninitialized when the third party code do his own cleaning. In this cases, check that you don't keep any references to interface objects beyond your application normal lifetime scope (ex. SomeGlobalCOMObject:=nil in a finalize section of a unit).

Whatever the cause here are a few methods to track down problems during unloading :
- add traces in finalize section of each unit you suspect have a role in this problem. The traces must be written on a file (you cannot use ShowMessage for instance to display some text while the application is unloading)
This will let you pinpoint which units are already unloaded when the problem happen, so that will give you entire files where the problem cannot be.
- add traces to each object destructor (the ones you find suspicious), might they be threads or any other objects. That is what devguru is talking about. By counting, per class, how many objects you create and how many you have destroyed, you can know how many there are left at any moment of your code. You can then make a function putting in a trace file these counts, and call that function in many strategic places in your unload code (ex. in main form destructor, after the project main Application.Run loop etc..)
- yet again stuff as many traces in your code as you see fit to home in on the bug like a missile.
You'll only to have a look at which trace is the last logged in your file when the problem occurs, and which one is the first that should be there but isn't. Then refine your search by adding some more traces between the 2.
It's ugly monkey work, but that is the only way I know to get rid of those bastard errors.
Freelance Project Manager
CERTIFIED EXPERT
Top Expert 2010
Commented:
trace to file function (add DEBUG_FILE_TRACE in conditional defines of your project while you want to debug, then remove it before releasing).

procedure FileTrace(M:String;FN:String='C:\TRACE.TXT');
Var F:TextFile;
begin
{$ifdef DEBUG_FILE_TRACE}
 AssignFile(F,FN);
 if FileExists(FN)
  Then Append(F)
  Else Rewrite(F);
 WriteLn(F,TimeToStr(Now)+' '+M);
 CloseFile(F);
{$endif}
end;

Open in new window

Examples :
unit SomeUnit;

Interface

Type
 TMyForm=class(TForm)
    procedure.FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
 end;
...
Implementation
...
Var
 TMyFormCount:Integer;

procedure TForm1.FormCreate(Sender: TObject);
begin
 FileTrace('TMyForm Created');
 Inc(TMyFormCount);
end;

procedure TMyForm.FormDestroy(Sender: TObject);
begin
 Dec(TMyFormCount);
 FileTrace('TMyForm Destroyed');
end;

initialization
 TMyFormCount:=0;
 FileTrace('SomeUnit Initialized');
finalization
 FileTrace('SomeUnit Finalized : '+IntToStr(TMyFormCount)+' TMyForm still unloaded');
end.

Open in new window

developmentguruPresident

Commented:
OK, I translated this from the following link:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms686852(v=vs.85).aspx

I did not have any means of testing the ErrorText method and I suspect that it might need work (not sure about 8 or 16 bit characters).  I was able to run the code on DelphiXE and get the list of running threads.  I also verified the list versus what the Delphi IDE showed as my list of running threads (1 during my test).

Create a new project and place a TList named lbThreads and a button named btnList on the form.  Copy the unit I have as shown and set the button OnClick to the one provided.  The current code shows the thread IDs in hex (change the formatting if desired).  Again, this was translated "on the fly".

I was glad to see that your question pushed me into areas I had not explored before.  While it is rare, it is one of the best reasons to be on here answering questions (It is good to be challenged).

Let me know if you need more.
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, TlHelp32;

type
  TForm1 = class(TForm)
    lbThreads: TListBox;
    btnList: TButton;
    procedure btnListClick(Sender: TObject);
  private
    { Private declarations }
    function ErrorText(Msg : string) : string;
    function ListThreads(List : TStrings) : boolean;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.btnListClick(Sender: TObject);
begin
  ListThreads(lbThreads.Items);
end;

function TForm1.ErrorText(Msg: string): string;
var
  ErrorNumber : DWORD;
  SysMsg : array[0..256] of Char;
  P : PChar;

begin
  ErrorNumber := GetLastError;
  FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM or FORMAT_MESSAGE_IGNORE_INSERTS,
    nil, ErrorNumber, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), SysMsg,
    256, nil);

  //trim the line and terminate with a null
  P := SysMsg;
  while (P^ > #31) or (P^ = #9) do
    inc(P);

  while (P >= SysMsg) and ((P^ = '.') or (P^ < #33)) do
    begin
      P^ := #0;
      dec(P);
    end;

  Result := Format('WARNING: %s failed with error %d (%s)', [Msg, ErrorNumber,
    SysMsg]);
end;

function TForm1.ListThreads(List: TStrings) : boolean;
var
  TE32 : ThreadEntry32;
  ThreadSnap : THandle;
  Line : string;

begin
  ThreadSnap := INVALID_HANDLE_VALUE;

  // Take a snapshot of all running threads
  ThreadSnap := CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
  if ThreadSnap = INVALID_HANDLE_VALUE then
    Result := false;

  // Fill in the size of the structure before using it.
  TE32.dwSize := sizeof(THREADENTRY32);

  // Retrieve information about the first thread,
  // and exit if unsuccessful
  if not Thread32First(ThreadSnap, &te32) then
    begin
      CloseHandle(ThreadSnap);     // Must clean up the snapshot object!
      raise Exception.Create(ErrorText('Thread32First'));
    end;

  // Now walk the thread list of the system,
  // and display information about each thread
  // associated with the specified process
  repeat
    if TE32.th32OwnerProcessID = GetCurrentProcessID then
      begin
        Line := Format('THREAD ID      = 0x%08X'#9, [TE32.th32ThreadID]);
        Line := Line + Format('base priority  = %d'#9, [te32.tpBasePri]);
        Line := Line + Format('delta priority = %d', [te32.tpDeltaPri]);
        List.Add(Line);
      end;
  until not Thread32Next(ThreadSnap, &TE32);
end;

end.

Open in new window

Author

Commented:
developmentguru,  That's exactly what I was looking for. At the bottom of the ListThreads function, what call would I use to terminate the thread rather than display it in the list?

My idea is to create a dll of this so it can be called from my Delphi 4 Project.  Yeah, our development tool is wayyy behind.
Thanks...
developmentguruPresident

Commented:
I have a couple of links for you on that...

http://support.microsoft.com/kb/254956
http://msdn.microsoft.com/en-us/library/windows/desktop/ms686717(v=vs.85).aspx

You need to be careful though as terminating a thread could cause memory leaks...
Emmanuel PASQUIERFreelance Project Manager
CERTIFIED EXPERT
Top Expert 2010

Commented:
I'm still unsure that your problems are thread-related, and that killing them would solve something.
But yeah, give it a try.

Author

Commented:
Great Job!!!  Thanks for your help, I've been starring at this error for a week now.
Emmanuel PASQUIERFreelance Project Manager
CERTIFIED EXPERT
Top Expert 2010

Commented:
Glad you found it !
Just out of curiosity, and also because it might inspire others having similar problems, can you explain shortly what you did to find the problem, where you found it, how you solved it ?

Author

Commented:
Well, I traced out of the code until I found the exception... it was hard getting there, previous developers had used exceptions logically (to determine for instance if a string is a date, or a float (try/except).  I found where a previous developer had assigned an address to a function to a var, was testing the var for nil and if not nil, used that address.   I commented out the portion of code that tested for nil and had it assign the value each time.  viola.... runtime error gone.  It was a trailing thread from our sqlQuery class generated via messaging.

I tried to assign some of the points to you because your info at the top helped organize my thoughts.  I have not implemented the thread killer routine (which works great !!), but am holding it in reserve :)

Thanks both of you, will try to assign points again.  I clicked the "was this comment helpful" on your info comment, it must have turned this issue into a discussion.

Author

Commented:
ok, my points were supposed to be awarded to DevelopmentGuru, but they seem to have been awarded to epasquire.  I awarded them because his solution matched my question, even though epasquire's comments were very helpful and the thread code was not necessary.

Explore More ContentExplore courses, solutions, and other research materials related to this topic.