Question

What is the best way to log messages from a thread?

Asked by: rossmcm

I have the need to log debug messages from various places, from the program main loop, and from running threads.  The debug messages eventually go into a TMemo component with a timestamp.

What is the best way to get my debug mesages from the thread to the Tmemo?  

I've used posted messages - too much setup overhead and baggage at the main loop end.

I've used Synchronize - the main disadvantage I see is that you have to pass any parameters by the back door.

I'd like to use critical sections, but I don't think wrapping the call to add the debug line to the memo with a CS would be sufficient.

So in greatly simplified form:

    procedure MyThread.LogDebugMessage (s : string) ;

    begin
    try
        CS.Enter ;
        MyMainForm.Memo1.Lines.Add (FormatDateTime ('c', Now) + ' ' + S) ;
    finally
        CS.Leave ;
        end ;
    end ;

    procedure MyThread.Execute ;

    begin
    LogDebugMessage ('Starting...') ;

    ....

What do I need to do to access the VCL without problems?

This Question has been solved and asker verified All Experts Exchange premium technology solutions are available to subscription members.

Subscribe now for full access to Experts Exchange and get

Instant Access to this Solution

  • Plus...
  • 30 Day FREE access, no risk, no obligation
  • Collaborate with the world's top tech experts
  • Unlimited access to our exclusive solution database
  • Never be left without tech help again

Subscribe Now

Asked On
2009-09-06 at 16:39:24ID24711546
Tags

delphi log debug thread

Topics

Delphi Programming

,

Delphi Components

Participating Experts
2
Points
100
Comments
14

Trusted by hundreds of thousands everyday for fast, accurate and reliable tech support.

  • "The time we save is the biggest benefit of Experts Exchange to Warner Bros. What could take multiple guys 2 hours or more each to find is accessed in around 15 minutes on Experts Exchange." Mike Kapnisakis, Warner Bros.
  • "Our team likes having a resource that is more secure than just using Google and most experts using this service really know their stuff. It's nice to look here first versus using Google." Dayna Sellner, Lockheed Martin
  • "Anytime that I've been stumped with a problem, 9 out of 10 times Experts Exchange has either the accepted solution or an open discussion of the potential solution to the problem." Kenny Red, eBay Inc.

See what Experts Exchange can do for you.

Got a question?

We've got the answer.

Experts Exchange has been collecting answers to technology questions since 1996…3 million and counting! If you have a question, chances are we already have your answer.

Screenshot of Experts Exchange Knowledgebase

Need individual assistance?

Our experts are ready to help.

If you can't find the exact answer you're looking for, ask our exclusive community of 50,000 experts. You’ll get a personalized answer from a trusted professional.

Screenshot of Experts Exchange Knowledgebase

Want to learn from the best?

Read articles from industry experts.

Thousands of free tech tips, tricks, how-to’s and tutorials are available in our peer reviewed articles section. See for yourself how smart our experts are, no login required.

Screenshot of an Article

Working on a long term project?

Store your work and research.

Save solutions to your questions, answers you’ve discovered through searching plus helpful articles in your personal knowledgebase for easy future access.

Screenshot of Experts Exchange Knowledgebase

Access the answers to your technology questions today.

Subscribe Now

30-day free trial. Register in 60 seconds.

What Makes Experts Exchange Unique?

Members of the expert community talk about why the experience at Experts Exchange is different than what you will find anywhere else.

Trusted by the world's most respected brands.

image of each brand's logo

Faithfully serving IT professionals since 1996.

Experts Exchange Logo

Try it out and discover for yourself.

Subscribe Now

30-day free trial. Register in 60 seconds.

Related Solutions

  1. Use of VCLs in threads.
    Is there any problems by using dynamicly created VCL classes in a TxxThread class? I guess that a call to the Application.ProcessMessages would not be good (another thread), but what do I do then?
  2. TMemo > 32k?
    I'm currently using Delphi v1 for a compiler IDE. But, when ever I try to open a file biggers than 32k, I receive the error "Text greater than 32k". Is there a way to overcome the TMemo and/or Win 3.x edit control limit? Thanks http://tcp.home.ml.org/
  3. background bitmap in TMemo
    how can I create backround from the bitmap in TMemo or how can I make lable type control witch will allow me to display text longer then 255 chars? I know how to create background in TLabel and similar components so it could be solution.
  4. Detecting URLs in TMemo
    I am using a TMemo in a mail client. I would like to implement the function of having URLs in the TMemo become links that can be clicked to launch web browser.
  5. adding Undo/Redo to a TMemo
    How can I add Undo and Redo functionality to a TMemo?
  6. Multi threaded app in BCB 4 (Synchronize <-> VCL)
    Hi, I wanted to use two threads to speed up things, but noticed (via help) that you cannot access VCL components without using Synchronize. I was wondering if that REALLY needs to be done when you make sure that you do not use the same VCL components at the same time ? Howev...

Free Tech Articles

  1. WARNING: 5 Reasons why you should NEVER fix a computer for free.
    It is in our nature to love the puzzle. We are obsessed. The lot of us. We love puzzles. We love the challenge. We thrive on finding the answer. We hate disarray. It bothers us deep in our soul. W...
  2. SCCM OSD Basic troubleshooting
    SCCM 2007 OSD is a fantastic way to deploy operating systems, however, like most things SCCM issues can sometimes be difficult to resolve due to the sheer volume of logs to sift through and the dispe...
  3. Migrate Small Business Server 2003 to Exchange 2010 and Windows 2008 R2
    This guide is intended to provide step by step instructions on how to migrate from Small Business Server 2003 to Windows 2008 R2 with Exchange 2010. For this migration to work you will need the fo...
  4. Create a Win7 Gadget
    This article shows you how to create a simple "Gadget" -- a sort of mini-application supported by Windows 7 and Vista. Gadgets can be dropped anywhere on the desktop to provide instant information, ...
  5. Outlook continually prompting for username and password
    There have been a lot of questions recently regarding Outlook prompting for a username and password whilst using Exchange 2007. There are a few reasons why this would happen and I will try to cover t...
  6. Backup Exchange 2010 Information Store using Windows Backup
    There seems to be quite a lot of confusion around the ability to backup Exchange 2010 using the built in Windows Backup feature. This stems from the omission of this feature prior to Exchange 2007 s...

Cloud Class Webinars

  1. Avoiding Bugs in Microsoft Access
    Alison Balter takes and in-depth look at avoiding bugs in Access. In this webinar you will learn about using the immediate window to debug your applications, invoking the debugger, using breakpoints to troubleshoot, stepping through code, setting the next statement to execute, ...
  2. Top 10 Best New Features in Visio 2010
    Scott Helmers gives live demonstrations of the top 10 new features in Visio 2010. This webinar will teach you how to create compelling diagrams by adding shapes to the page with a single click, linking the shapes in a diagram to data in Excel (or SQL Server, or SharePoint), ...
  3. IT Consultant Business Secrets Revealed
    Michael Munger, Experts Exchange tech pro and IT consultant, pulls back the curtain on his very successful businesses and answers question on every IT consultant and business owner should know about. He shares secrets on what he did to solve the 5 most common problems in IT, ...
  4. Disaster Recovery and Business Continuity
    Quest CTO, Mike Billon, gives an overview of the steps involved in building a dunamic disaster recovery plan. Through case studies and an examination of software/hardware tooles for monitoring and testing, you'll gain a better understandin of where you are, where you want ...
  5. Organize Your Visio Diagrams with Containers and Lists
    Scott Helmers uses cross functional flowcharts, wireframe diagrams, data graphic legends and seating charts to teach you: how to ustilize all three new structured diagram components in Visio 2010, the best practices for organizeing shapes in previous version of Visio, how to organize ...
  6. How to Us Objects, Properties, Events and Methods in Microsoft Access
    Alison Dalter gives an in-depbth look at objects, properties, events and methods in Microsoft Access. In this webinar you will learn about using the object browser, referring to objects, working with properties and methods, working with object variables, understanding the ...

Join the Community

Give a Little. Get a Lot.

Join the community of experts here and help other tech pros by answering question in your area of expertise. You can earn FREE access to all Experts Exchange's premium features and resources.

Join the Community

Answers

 

by: Geert_GruwezPosted on 2009-09-06 at 23:18:02ID: 25273004

your main problem is you use a TMemo --> this is a TStringlist --> this is slow
this is overhead !
next to that, you are using a form as well and wanting it to be called MyMainForm

you should use a file to log messages
the one thing you can allways rely on : there is allways a file system.
the best thing is to log messages to a file and
if you want to see the log messages, only then open the file in a TMemo
(possibly add a refresh to reload the file)

with the unit in snippet you have 1 procedure to add text to a log from anywhere

AddLog('This a message from Thread 1', True, 'thread1');

You can change the 'thread1' to anything specific like 'debug'
this way you can separate different message logs

the log files are created with a start timestamp
when they go over a certain size (default 1024Kb) , a new file is started
when files are over 30 days and over 1024Kb they are also cleaned up
default in .exe directory

unit uLogging;
 
interface
 
procedure AddLog(Msg: string; DateTimeLinePrefix: boolean = True; LogPrefix: string = '');
 
implementation
 
uses Classes, SysUtils, SyncObjs;
 
var cs: TCriticalSection;
 
procedure AddToLog(LOG_PATH: string; LOG_MAXSIZE: integer; LOG_LIFETIME: double;
  Msg: string; LogPrefix: string = ''; DateTimeLinePrefix: Boolean = True);
var
  sr: TSearchRec;
  LogFile: TextFile;
  ValidFile, SearchPath: string;
  valid: boolean;
  j: integer;
  TimeStr : string;
begin
  cs.Enter;
  try
    SearchPath := IncludeTrailingPathDelimiter(Trim(LOG_PATH));
    if not DirectoryExists(SearchPath) then
      ForceDirectories(SearchPath);
    ValidFile := '';
    if FindFirst(format('%s%s*.log', [SearchPath, LogPrefix]), faAnyFile, sr) = 0 then
    try
      repeat
        if sr.Size < LOG_MAXSIZE * 1024 then
          ValidFile := sr.Name
        else
          if FileDateToDateTime(sr.Time) < Now - LOG_LIFETIME then
            DeleteFile(Pchar(SearchPath + sr.Name));
      until FindNext(sr) <> 0;
    finally
      FindClose(sr);
    end;
    try
      if ValidFile <> '' then
      begin
        j := 0;
        repeat
          try
            AssignFile(LogFile,format('%s%s',[SearchPath, ValidFile]));
            Append(LogFile);
            valid := true;
          except
            Inc(j);
            valid := false;
            sleep(500);
          end;
        until valid or (j > 2);
      end
        else
      begin
        j := 0;
        repeat
          try
            Rewrite(Logfile, Format('%s%s_%s.log', [SearchPath, LogPrefix, FormatDateTime('ddmmhhnn',now)]));
            valid := true;
          except
            Inc(j);
            valid := false;
            sleep(500);
          end;
        until valid or (j > 2);
      end;
      j := 0;
      repeat
        try
          if DateTimeLinePrefix then
          begin
            DateTimeToString(TimeStr, 'DD/MM/YYYY HH:NN:SS', Now);
            Msg := TimeStr + ' - ' + Msg;
          end;
          Writeln(LogFile, Msg);
          valid := true;
        except
          Inc(j);
          valid := false;
          sleep(500);
        end;
      until valid or (j > 2);
    finally
      CloseFile(LogFile);
    end;
  finally
    cs.Leave;
  end;
end;
 
procedure AddLog(Msg: string; DateTimeLinePrefix: boolean = True; LogPrefix: string = '');
begin
  AddToLog(ExtractFilePath(ParamStr(0)), 1024, 30, Msg, LogPrefix, DateTimeLinePrefix);
end;
 
initialization
  cs := TCriticalSection.Create;
finalization
  cs.Free;
end.
 

                                              
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:

Select allOpen in new window

 

by: rossmcmPosted on 2009-09-07 at 04:22:27ID: 25274232

Thanks for your comprehensive reply.  My example was deliberately simplified.  I need to see the debug messages in real time and don't want to log them to a file..   I already use a TMemo to log debug messages from the main thread and it is plenty fast enough.  My problem or my question is really "how can I log messages from another thread to a memo using critical sections?"

Ross

 

by: Geert_GruwezPosted on 2009-09-07 at 06:42:44ID: 25275004

just a remark: your try finally is wrong
CS.Enter must be before the try, not after
-----------
CS.Enter
try
...
-----------
and not like this
//-----------
//try
//CS.Enter
//...
//------------

ah
the problem is *showing* the message in a form
that form (vcl) is not thread safe

a critical section is used to protect a resource from being changed by only 1 thread at a time

if you apply this to your situation

you have a thread which creates a message
this message must be shown in a memo on a form
FIFO structure --> thread message in buffer --> vcl form message out buffer

you thus need to protect your buffer with the CS
most simple approach (use a string as buffer)

type
  TMyThread = class(TThread)
  private
    fFormMsgHandle: THandle;
  public
    constructor Create(CreateSuspended: Boolean; aFormMsgHandle: THandle);
  end;
 
constructor TMyThread.Create(CreateSuspended: Boolean; aFormMsgHandle: THandle);
begin
  inherited Create(CreateSuspended);
  fFormMsgHandle := aFormMsgHandle;
end;
 
procedure MyThread.LogDebugMessage (s : string) ;
begin
  CS.Enter;
  try
    if Buffer = '' then 
      Buffer := s
    else 
      Buffer := Buffer + #13#10 + s;
  finally
    CS.Leave;
  end;
  PostMessage(fFormMsgHandle, WM_USER, 0, 0); // Notify form of message in buffer
end; 
        
                                              
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:

Select allOpen in new window

 

by: Geert_GruwezPosted on 2009-09-07 at 06:44:55ID: 25275018

and from form side

type
  TForm1 = class(TForm)
  private
  protected
    procedure WMUser(var Msg: TMessage); message WM_USER;
  public
  
  end;
 
procedure TForm1.StartThread;
begin
  aThread := TMyThread.Create(False, Self.Handle);
end;
 
procedure TForm1.WMUser(var Msg: TMessage); 
begin
  CS.Enter;
  try
    MyMainForm.Memo1.Lines.Add (FormatDateTime ('c', Now) + ' ' + Buffer);
    Buffer := '';
  finally
    CS.Leave;
  end;
end ;
                                              
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:

Select allOpen in new window

 

by: Geert_GruwezPosted on 2009-09-07 at 08:18:09ID: 25275607

uh ... typo

procedure TForm1.WMUser(var Msg: TMessage);
begin
  CS.Enter;
  try
    Memo1.Lines.Add (FormatDateTime ('c', Now) + ' ' + Buffer);
    Buffer := '';
  finally
    CS.Leave;
  end;
end ;

 

by: rossmcmPosted on 2009-09-07 at 13:36:44ID: 25277153

You've got the points incidentally.  Full marks for effort!

OK, that's sort of what I have implemented in the past, except that you seem to be passing "empty" messages and using them only as a trigger to pick something up from the string FIFO, whereas I created message records and passed the info in them.  Do you have any comment on which is preferable?  Your method is simpler but doesn't lead itself so easily to passing information other than strings.

As an example I also have the need to pass progress messages, which might consist of:

    count done so far
    total count to be done
    message string

I guess I could similarly put those values into a CS-wrapped record and fire a message off.  It would also enable me to implement 2-way communicationn between the thread and the main program, which would allow be to abort the thread operation:

I'm interested as to why you think it is necessary to put the CS.Enter before the TRY?

//  in thread
    procedure ReportProgress (BytesSoFar : integer ; TotalBytes: integer ; Msg : string ; var ABortRequest : boolean) ;
 
    begin
    CS.Enter
    // put parameters into mailbox
    mbSoFar := BytesSoFar ;
    mbOutOf := TotalBytes ;
    mbMsg    := Msg ;
 
    // read back abort flag
    AbortRequest := mbAbort ;
    CS.Leave  
    // fire off message to main form
    end ;
 
    begin // execute
    repeat
        ....
        ReportProgress (ByteSoFar, TotalBytesToRead, 'Reading ...', AbortMe) ;
        ....
     until finished or AbortMe ;
    end ;
 
// in the main loop
 
    on message handler, grab the byte counts and string and show them:
 
    on abort signal, set mbAbort := true 
                                              
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:

Select allOpen in new window

 

by: Geert_GruwezPosted on 2009-09-07 at 23:44:22ID: 25279135

I use D2009
-->StrictDelimiter may not exist for earlier version
// uncomment it if the compiler complains

let's expand and use a TStrings decendant as buffer
with a TStrings descendant you can use Names and ValueFromIndex ...

type
  TForm1 = class(TForm)
    Memo1: TRichEdit;
    BitBtn1: TBitBtn;
    procedure BitBtn1Click(Sender: TObject);
  private
    procedure WMUser(var Msg: TMessage); message WM_USER;
  end;
 
  TMyThread = class(TThread)
  private
    fFormMsgHandle: THandle;
  protected
    procedure Execute; override;
    procedure LogDebugMessage(aMsg : string);
  public
    constructor Create(CreateSuspended: Boolean; aFormMsgHandle: THandle);
  end;
 
var
  Form1: TForm1;
 
implementation
 
uses SyncObjs;
 
{$R *.dfm}
 
var
  mMsgBuffer: TStrings;
  CS: TCriticalSection;
 
function LockBuffer: TStrings;
begin
  CS.Enter;
  if not Assigned(mMsgBuffer) then
  begin
    mMsgBuffer := TStringList.Create;
    mMsgBuffer.StrictDelimiter := True;
  end;
  Result := mMsgBuffer;
end;
 
procedure UnlockBuffer;
begin
  CS.Leave;
end;
 
constructor TMyThread.Create(CreateSuspended: Boolean; aFormMsgHandle: THandle);
begin
  inherited Create(CreateSuspended);
  fFormMsgHandle := aFormMsgHandle;
end;
 
procedure TMyThread.LogDebugMessage(aMsg : string);
begin
  with LockBuffer do
  try
    Add(aMsg);
  finally
    UnlockBuffer;
  end;
  PostMessage(fFormMsgHandle, WM_USER, 0, 0); // Notify form of message in buffer
end;
 
procedure TMyThread.Execute;
var Msg: TStrings;
  I: Integer;
begin
  // fast way :
  // LogDebugMessage('Progress=Min=0,Max=100,Value=0,Msg="Starting thread"');
  // easy way :
  Msg := TStringList.Create;
  try
    Msg.Clear;
    Msg.StrictDelimiter := True;
    Msg.Values['Min'] := '0';
    Msg.Values['Max'] := '100';
    Msg.Values['Value'] := '0';
    Msg.Values['Msg'] := 'Starting thread';
    LogDebugMessage('progress=' + Msg.DelimitedText);
    for I := 1 to 100 do
    begin
      Sleep(500);
      Msg.Values['Value'] := IntToStr(I);
      Msg.Values['Msg'] := 'Meter running : ' + IntToStr(I * 10) + '$';
      LogDebugMessage('progress=' + Msg.DelimitedText);
    end;
    Msg.Values['Msg'] := 'Thread finished';
    LogDebugMessage('progress=' + Msg.DelimitedText);
  finally
    Msg.Free;
  end;
end;
 
procedure TForm1.BitBtn1Click(Sender: TObject);
begin
  TMyThread.Create(False, Handle);
end;
 
procedure TForm1.WMUser(var Msg: TMessage);
var I, J: Integer;
  temp: TStrings;
begin
  with LockBuffer do
  try
    temp := TStringList.Create;
    try
      temp.StrictDelimiter := True;
      for I := 0 to Count - 1 do
      begin
        if SameText(Names[I], 'progress') then
        begin
          temp.DelimitedText := ValueFromIndex[I];
          Memo1.Lines.Add('Min   = ' + temp.Values['Min']);
          Memo1.Lines.Add('Max   = ' + temp.Values['Max']);
          Memo1.Lines.Add('Value = ' + temp.Values['Value']);
          Memo1.Lines.Add('Msg   = ' + temp.Values['Msg']);
        end;
      end;
    finally
      temp.Free;
    end;
  finally
    UnlockBuffer;
  end;
end;
 
initialization
  CS := TCriticalSection.Create;
finalization
  FreeAndNil(mMsgBuffer);
  CS.Free;
end.
                                              
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:

Select allOpen in new window

 

by: Geert_GruwezPosted on 2009-09-07 at 23:57:02ID: 25279185

try finally explanation
this is something widely misunderstood by a lot of delphi coders ...

try is a translation of :
if the previous line of code worked then no matter what happens after this line allways execute the code in the finally part

>>if the previous line of code worked
if it wouldn't then you would get a exception on that line and you would never get at the try line

>>then no matter what happens after this line
the code between try and finally can throw anything it want (exit, break, exception etc)

>>allways execute the code in the finally part
only if we got to past the try line

sample:
wrong:
  try
    GetMem(X, 5000); // This throws a exception
  finally
    FreeMem(X, 5000); // gets executed anyway and throws a exception too !!
  end;


correct:
  GetMem(X, 5000); // This throws a exception
  try
    // Copy(Y, X, 5000);
  finally
    FreeMem(X, 5000); // doesn't get executed because we didn't get past the try
  end;

 

by: Geert_GruwezPosted on 2009-09-08 at 00:07:14ID: 25279231

>>Do you have any comment on which is preferable
you can post several messages in different threads
the main form could receive a WM_USER message and clear out the remaining ones

 

by: Geert_GruwezPosted on 2009-09-08 at 00:09:19ID: 25279239

incidently,
you could add thread identification in your message ...

  Msg := TStringList.Create;
  try
    Msg.Clear;
    Msg.StrictDelimiter := True;
    Msg.Values['Thread'] := 'TMyThread';
    Msg.Values['Min'] := '0';

 

by: rossmcmPosted on 2009-09-08 at 02:27:16ID: 31625541

Geert put in a lot of effort for not many points.

 

by: TheRealLokiPosted on 2009-09-08 at 14:00:25ID: 25286109

I know this post is over, but thought I'd add my 2c :-)
here's a common method I use for simple applications

For services, I use a far more complicated TCriticalSection approach, but this is fine for most apps.

unit Unit1;
 
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;
 
Const
  WM_LogMessage = WM_user + 100;
// used in the lParam for threads sending log messages
  tlmtinformation = 1;
  tlmtWarning = 2;
  tlmtError = 4;
// used in the lParam for threads sending log messages
  tldNone = 8;
  tldIn = 16;
  tldOut = 32;
// same as above, but used in the main thread's LogMessage() procedure
type TLogMessageType = (lmtInformation, lmtWarning, lmtError);
type TLogDirection = (ldNone, ldIn, ldOut);
 
type TWorkerThread = class(TThread)
  protected
    procedure execute; override;
  public
    ThreadName: string;
    LogHandle: THandle;
    Frequency: integer;
    Iterations: integer;
    constructor Create(CreateSuspended: Boolean; ThreadName_: string; LogHandle_: THandle; Frequency_, Iterations_: integer);
    procedure LogThreadMessage(LogMessageType: TLogMessageType; LogDirection: TLogDirection; S: string);
  end;
 
type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    bStartThreads: TButton;
    bStopThreads: TButton;
    procedure bStartThreadsClick(Sender: TObject);
    procedure bStopThreadClick(Sender: TObject);
  private
    { Private declarations }
    WatchedThread: TWorkerThread;
    procedure LogMessage(LogMessageType: TLogMessageType;
      LogDirection: TLogDirection; S: string);
    procedure StopThread;
  public
    { Public declarations }
    Procedure Message_Log(var Msg:TMessage);Message WM_LogMessage;
  end;
 
var
  Form1: TForm1;
 
implementation
 
{$R *.DFM}
 
 
 
{ TForm1 }
 
procedure TForm1.Message_Log(var Msg: TMessage);
var
  PS:PString;
  S: string;
  i: integer;
  LogMessageType: TLogMessageType;
  LogDirection: TLogDirection;
begin
 
  PS:=Pointer(Msg.WParam);
  S := PS^;
 
  i := Msg.LParam;
  if (i and tlmtinformation) = tlmtinformation then LogMessageType := lmtInformation;
  if (i and tlmtwarning) = tlmtwarning then LogMessageType := lmtWarning;
  if (i and tlmterror) = tlmterror then LogMessageType := lmtError;
 
  if (i and tldNone) = tldNone then LogDirection := ldNone;
  if (i and tldIn) = tldIn then LogDirection := ldIn;
  if (i and tldOut) = tldOut then LogDirection := ldOut;
  LogMessage(LogMessageType, LogDirection, S);
 
  //        FS.WriteBuffer(S^[1],Length(S^));
  Dispose(PS);
end;
 
procedure TForm1.LogMessage(LogMessageType: TLogMessageType; LogDirection: TLogDirection; S: string);
var
  FullMsg: string;
begin
  FullMSg := '';
  case LogMessageType of
    lmtInformation: FullMsg := '  ';
    lmtWarning: FullMsg := '? ';
    lmtError: FullMsg := '!  ';
  end;
 
  case LogDirection of
    ldNone: FullMsg := FullMsg + '   ';
    ldIn: FullMsg := FullMsg + '<- ';
    ldOut: FullMsg := FullMsg + '-> ';
  end;
  FullMsg := FullMsg + FormatDateTime('hh":"nn":"ss', Now) + ' ' + S;
  Listbox1.Items.Add(FullMsg);
  while Listbox1.Items.Count > 1000 do Listbox1.Items.Delete(0);
  Listbox1.ItemIndex := pred(Listbox1.Items.Count);
end;
 
procedure TForm1.bStartThreadsClick(Sender: TObject);
begin
  if assigned(WatchedThread) then
  begin
    StopThread;
  end;
  WatchedThread := TWorkerThread.Create(false, 'Watched Thread', self.Handle, 1000, 10);
  TWorkerThread.Create(false, 'Thread 1', self.handle, 1000, 5);
  TWorkerThread.Create(false, 'Thread 2', self.handle, 2000, 5);
  TWorkerThread.Create(false, 'Thread 3', self.handle, 100, 10);
end;
 
procedure TForm1.bStopThreadClick(Sender: TObject);
begin
  StopThread;
end;
 
procedure TForm1.StopThread;
begin
  if assigned(WatchedThread) then
  begin
    WatchedThread.Terminate;
    WatchedThread.Waitfor;
    WatchedThread := nil;
  end;
end;
 
{ TWorkerThread }
 
constructor TWorkerThread.Create(CreateSuspended: Boolean; ThreadName_: string; LogHandle_: THandle; Frequency_,
  Iterations_: integer);
begin
  inherited Create(CreateSuspended);
  ThreadName := ThreadName_;
  Frequency := Frequency_;
  Iterations := Iterations_;
  LogHandle := LogHandle_;
  FreeOnTerminate := True;
  LogThreadMessage(lmtInformation, ldNone, 'Started');
end;
 
procedure TWorkerThread.execute;
var
  i: integer;
begin
  i := 0;
  try
    while (not terminated) and (i < self.Iterations) do
    begin
      sleep(self.Frequency);
      inc(i);
      LogThreadMessage(lmtInformation, ldNone, IntToStr(i));
    end;
    if (terminated) then
      raise exception.Create('asked to terminate! - this is just for demo-ing...');
  except
    on e: exception do
    begin
     // sample only
      LogThreadMessage(lmtError, ldNone, e.Message);
    end;
  end;
  LogThreadMessage(lmtInformation, ldNone, 'Finished');
end;
 
procedure TWorkerThread.LogThreadMessage(LogMessageType: TLogMessageType;
  LogDirection: TLogDirection; S: string);
var
  i: Integer;
  PS: PString;
begin
  New(PS);
  PS^ := Format('(%s)%s', [self.ThreadName, S]);
  i := 0;
  if LogMessageType = lmtInformation then i := tlmtInformation;
  if LogMessageType = lmtWarning then i := tlmtWarning;
  if LogMessageType = lmtError then i := tlmtError;
 
  if LogDirection = ldNone then i := i + tldNone;
  if LogDirection = ldIn then i := i + tldIn;
  if LogDirection = ldOut then i := i + tldOut;
  PostMessage(LogHandle, WM_LogMessage, Integer(PS), i);
end;
 
end.
 
 
**********************
***  FORM FOLLOWS  ***
**********************
 
object Form1: TForm1
  Left = 249
  Top = 183
  Width = 785
  Height = 425
  Caption = 'Form1'
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object ListBox1: TListBox
    Left = 16
    Top = 52
    Width = 749
    Height = 289
    Font.Charset = ANSI_CHARSET
    Font.Color = clWindowText
    Font.Height = -11
    Font.Name = 'Courier New'
    Font.Style = []
    ItemHeight = 14
    ParentFont = False
    TabOrder = 0
  end
  object bStartThreads: TButton
    Left = 20
    Top = 8
    Width = 75
    Height = 25
    Caption = 'bStartThreads'
    TabOrder = 1
    OnClick = bStartThreadsClick
  end
  object bStopThreads: TButton
    Left = 132
    Top = 8
    Width = 75
    Height = 25
    Caption = 'bStopThreads'
    TabOrder = 2
    OnClick = bStopThreadClick
  end
end

                                              
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
187:
188:
189:
190:
191:
192:
193:
194:
195:
196:
197:
198:
199:
200:
201:
202:
203:
204:
205:
206:
207:
208:
209:
210:
211:
212:
213:
214:
215:
216:
217:
218:
219:
220:
221:
222:
223:
224:
225:
226:
227:
228:
229:
230:
231:
232:
233:
234:
235:
236:
237:
238:
239:
240:
241:
242:
243:
244:
245:
246:
247:
248:
249:

Select allOpen in new window

 

by: Geert_GruwezPosted on 2009-09-08 at 23:13:01ID: 25288596

>>ThievingSix
nice sample

>>rossmcm
2 way communication ?
interesting idea
i usually start from the idea that i give a thread something to do just info on it's progress or status
if i have to alter the behaviour of the thread
then this thread would have to read a config block at the start of the loop
which a other thread would have altered
once the loop is started the only options would be break or halt

 

by: rossmcmPosted on 2009-09-09 at 03:04:11ID: 25289614

The application is also reading data from a remote device over a serial link, so I had to be able to tell the thread to finish if someone clicks cancel.

The scheme I have finally come up with is to:

  - create a global thread-safe boolean AbortMe using a CS. Set it true if someone clicks on cancel.
  - create message records in the thread, fill them in and PostMessage them.  As this routine is called from the thread, I also read AbortMe and return that as a var parameter back to the thread.  If it is true the thread exits.
  - in the main loop handle the posted messages and provide a progress display.

See below (simplified).

//  main thread ==========================================================
//  AbortMe is thread-safe CS boolean
 
// On button click:
  AbortMe := true ;
 
// On Callback from worker thread:
 
  procedure ThreadCallback (Sender : TObject ;
                            SoFar  : integer ;
                            OutOf  : integer ;
                            Msg    : string ;
                        var Abort  : boolean) ;
  begin
  ..create message record, fill in from parameters
  ..post message to main form
 
  if AbortMe then
    AbortMe := false ;
    Abort := true ;
  end ;     
 
// Message Handler:
 
  procedure MainForm.ThreadProgressMessageHandler (var Message : ...; 
 
  begin
  ..get message data
  ..update progress and status display in UI
  end ;
 
//  Worker thread =======================================================
 
procedure TWorkerThread.Execute ;
 
var
    Abort : boolean ;
begin
Abort := false ;
repeat
    read block of data
    inc (BlocksRead)
 
    if Assigned (Callback) then
        Callback (Self, BlocksRead, TotalBlocks, 'Reading...', Abort) ;
    if Abort then
        ThreadResult := ThreadAborted ;
        break ;
    if (BlocksRead >= TotalBloack) then
        ThreadResult := ThreadOK ;
        break ;
until done
end ;

                                              
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:

Select allOpen in new window

20120131-EE-VQP-002

3 Ways to Join

30-Day Free Trial

The Experts

98% positive feedback on 31,087 answers since March 2000. angeliii is a Microsoft Most Valuable Professional for his work with MS SQL Server & Develoment.

He has also proven his knowledge of Visual Basic Programming, PHP Scripting and Oracle Databases.

The Experts

97% positive feedback on 10,752 answers since July 2000. lrmoore has more than 18 years experience in the networking industry.

The six-time Mircosoft MVPs specialties include firewalls, virtual private networking, and network management.

Testimonials

"...and excellent source for support... Kind of like having your very own IT dept." Electriciansnet

Testimonials

"I was apprehensive at signing up at first. However... it has already made my life as an IT administrator much easier." JaCrews

Testimonials

"WOW! You guys have great, active, and knowledgeable people on here." moore50

Business Clients

Business Clients

In the Press

"If you’ve got a question... Experts Exchange can supply an answer.”

In the Press

"...an invaluable aid for both IT professionals and those who require tech support."

In the Press

"where IT professionals provide quick answers on just about any topic"

Business Account Plans

Loading Advertisement...