Multiple TThread Instances - multithreading

I have a TThread Class that can run independently and terminates and frees itself after it's done. I considered the termination and everything works. The problem is, that I would like to add a feature that the user can select and choose how many SYNCHRONOUS threads should be active at the same time. An example would be:
The program has to do 100 total tasks!
The user chooses 3 Threads should be running at the same time to complete all the tasks.
The first step I did was to create 3 instances of my TThread Class and the resume them in a for loop. So 3 threads are running. After the first thread is done (or terminated), another new instance needs to be created and resumed.
I get stuck on this point and I wonder how I can realize this.
Any advice would be helpful.
Edit: Some Code
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
type
TMyThread = class (TThread)
public
procedure Execute; override;
end;
var
Form1 : TForm1;
Threads : Integer = 3;
TotalTasks : Integer = 100;
implementation
{$R *.dfm}
procedure TMyThread.Execute;
begin
// some work...
sleep (2000 + random (5000));
end;
function DummyThread ( p : pointer ) : Integer; stdcall;
var
NewInstanceOfTMyThread : TMyThread;
I : Integer;
begin
for I := 1 to Threads do begin
with TMyThread.Create (TRUE) do begin
resume;
end;
end;
// Here should be code to detect if a new thread has to be started, etc.
end;
// We start the task to start the tasks...
procedure TForm1.Button1Click(Sender: TObject);
var
ThreadID : DWORD;
begin
CloseHandle (CreateThread(NIL, 0, #DummyThread, NIL, 0, ThreadID));
end;
end.

You can write a handler for OnTerminate event of TThread. The handler should start/resume a new thread.
Or you can have 3 threads which are running constantly and take tasks from some queue (just take care of synchronizing the queue access).

Related

Reading thread's fields from synchronized event handler

Is it safe to read a thread object's fields from an event handler called by the Synchronize procedure?
For example:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls;
type
TMyThread = class(TThread)
public
Max : Integer;
Position : Integer;
OnPositionChanged : TNotifyEvent;
procedure Execute(); override;
end;
TForm1 = class(TForm)
ProgressBar1: TProgressBar;
procedure FormCreate(Sender: TObject);
private
procedure MyOnPositionChanged(Sender : TObject);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
var
Th : TMyThread;
procedure TMyThread.Execute();
begin
while not Terminated do
begin
//doing stuffs
Sleep(500);
//position + 1
Inc(Position);
//event handler
if(Assigned(OnPositionChanged)) then
begin
Synchronize(
procedure()
begin
OnPositionChanged(Self);
end
);
end;
//check for reaching the max value
if(Position = Max)
then Terminate;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
//preparing thread
Th := TMyThread.Create(True);
Th.FreeOnTerminate := True;
Th.Max := ProgressBar1.Max;
Th.Position := ProgressBar1.Position;
Th.OnPositionChanged := MyOnPositionChanged;
//starting thread
Th.Start;
end;
procedure TForm1.MyOnPositionChanged(Sender : TObject);
begin
//updating progressbar
ProgressBar1.Position := (Sender as TMyThread).Position;
end;
end.
I'm wondering if there could be some thread-safety problem in reading the thread's fields from the main thread while the other thread is running
Yes, this is generally safe. The thread's Execute() method is blocked while Synchronize() is running, so the thread won't be updating the fields while the main thread is using them.
Where this can break down is if you happen to have another thread updating the same fields without Synchronize()'ing access to them.

multithreading in Delphi

I have a number crunching application with a TExecution class that is included in a separate unit Execution.pas and carries out all the calculations. The class instances are created from the main form of the program. Very often the code in Execution.pas needs to run 10-15 times in a row and I want to create several TExecution instances in different threads and run them in parallel. A simplified version of the code is as follows:
Main Form with one Button1 in it:
unit MainForm;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.Threading, Execution;
type
TMainForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
end;
var
MainForm1: TMainForm1;
implementation
{$R *.dfm}
procedure TMainForm1.Button1Click(Sender: TObject);
var
ExecutionThread: array of TThread;
NoThreads: integer;
Execution: array of TExecution;
thread_ID: integer;
begin
NoThreads := 5;
SetLength(Execution,NoThreads);
SetLength(ExecutionThread,NoThreads);
//----------------------------------------------------------------------------------
for thread_ID := 0 to Pred(NoThreads) do
begin
ExecutionThread[thread_ID] := TThread.CreateAnonymousThread(
procedure
begin
try
Execution[thread_ID] := TExecution.Create;
Execution[thread_ID].CalculateSum;
finally
if Assigned(Execution[thread_ID]) then
begin
Execution[thread_ID] := nil;
Execution[thread_ID].Free;
end;
end;
end);
ExecutionThread[thread_ID].FreeOnTerminate := true;
ExecutionThread[thread_ID].Start;
end;
end;
end.
Execution.pas unit:
unit Execution;
interface
uses
System.SysUtils, Vcl.Dialogs, System.Classes, WinApi.Windows;
type
TExecution = Class
const
NoOfTimes = 1000000;
var
Sum: integer;
private
procedure IncrementSum(var Sum: integer);
published
procedure CalculateSum;
End;
implementation
procedure TExecution.CalculateSum;
var
i: integer;
begin
Sum := 0;
for i := 0 to Pred(NoofTimes) do
begin
IncrementSum(Sum);
end;
end;
procedure TExecution.IncrementSum(var Sum: integer);
begin
Inc(Sum);
end;
end.
Whenever I run the code above by clicking Button1 the TExecution instances run, but when I close the program, I get an Access Violation in GetMem.inc in function SysFreeMem. Obviously, the code messes up the memory, I guess it is because of the parallel memory allocation, but I was unable to find the cause and fix a solution to it.
I note that with one thread (NoThreads := 1), or with a serial execution of the code (either with a single new thread and 5 TExecution instances, or when the instances of TExecution are created directly from MainForm), I do not get similar memory problems. What is the problem with my code?
Many thanks in advance!
The problem comes from ExecutionThread and Execution which are local variables. When all threads are started, the procedure Button1Click exits, the two variables are freed, long before threads are terminated.
Move the two variables ExecutionThread and Execution to the TMainForm1 field and your problem will be gone. Of course: if you close the program before the threads are terminated, you'll be again in trouble.
Also, invert the two lines:
Execution[thread_ID] := nil;
Execution[thread_ID].Free;
You must free before niling.
BTW: You should get a compiler warning about published in TExecution.
EDIT:
Following the comment on this answer, here is the code for the same process but using an explicit worker thread and a generic TList to maintain the list of running thread.
Source for the main form:
unit ThreadExecutionDemoMain;
interface
uses
Winapi.Windows, Winapi.Messages,
System.SysUtils, System.Variants, System.Classes,
System.Generics.Collections,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
ThreadExecutionDemoExecution,
ThreadExecutionDemoWorkerThread;
type
TMainForm = class(TForm)
StartButton: TButton;
DisplayMemo: TMemo;
procedure StartButtonClick(Sender: TObject);
private
ThreadList : TList<TWorkerThread>;
procedure WrokerThreadTerminate(Sender : TObject);
public
constructor Create(AOwner : TComponent); override;
destructor Destroy; override;
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
constructor TMainForm.Create(AOwner: TComponent);
begin
ThreadList := TList<TWorkerThread>.Create;
inherited Create(AOwner);
end;
destructor TMainForm.Destroy;
begin
FreeAndNil(ThreadList);
inherited Destroy;;
end;
procedure TMainForm.StartButtonClick(Sender: TObject);
var
NoThreads : Integer;
ID : Integer;
WorkerThread : TWorkerThread;
begin
NoThreads := 5;
for ID := 0 to Pred(NoThreads) do begin
WorkerThread := TWorkerThread.Create(TRUE);
WorkerThread.ID := ID;
WorkerThread.OnTerminate := WrokerThreadTerminate;
WorkerThread.FreeOnTerminate := TRUE;
ThreadList.Add(WorkerThread);
DisplayMemo.Lines.Add(Format('Starting thread %d', [WorkerThread.ID]));
WorkerThread.Start;
end;
DisplayMemo.Lines.Add(Format('There are %d running threads', [ThreadList.Count]));
end;
procedure TMainForm.WrokerThreadTerminate(Sender: TObject);
var
WorkerThread : TWorkerThread;
begin
WorkerThread := TWorkerThread(Sender);
ThreadList.Remove(WorkerThread);
// This event handler is executed in the context of the main thread
// we can access the user interface directly
DisplayMemo.Lines.Add(Format('Thread %d done. Sum=%d',
[WorkerThread.ID, WorkerThread.Sum]));
if ThreadList.Count = 0 then
DisplayMemo.Lines.Add('No more running threads');
end;
end.
Source for the execution unit:
unit ThreadExecutionDemoExecution;
interface
type
TExecution = class
const
NoOfTimes = 1000000;
private
FSum: Integer;
procedure IncrementSum(var ASum: Integer);
public
procedure CalculateSum;
property Sum: Integer read FSum
write FSum;
end;
implementation
{ TExecution }
procedure TExecution.CalculateSum;
var
I: Integer;
begin
FSum := 0;
for I := 0 to Pred(NoOfTimes) do
IncrementSum(FSum);
end;
procedure TExecution.IncrementSum(var ASum: Integer);
begin
Inc(ASum);
end;
end.
Source for the worker thread:
unit ThreadExecutionDemoWorkerThread;
interface
uses
System.SysUtils, System.Classes,
ThreadExecutionDemoExecution;
type
TWorkerThread = class(TThread)
private
FExecution : TExecution;
FID : Integer;
FSum : Integer;
protected
procedure Execute; override;
public
property ID : Integer read FID
write FID;
property Sum : Integer read FSum
write FSum;
end;
implementation
{ TWorkerThread }
procedure TWorkerThread.Execute;
begin
FExecution := TExecution.Create;
try
FExecution.CalculateSum;
FSum := FExecution.Sum;
finally
FreeAndNil(FExecution);
end;
end;
end.

Thread variable lost / changed after passing thread object to another class?

I wrote a little program that shows my problem:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private-Deklarationen }
public
{ Public-Deklarationen }
end;
TThread_A = class(TThread)
private
stupidvariable : integer;
protected
procedure Execute; override;
public
property getstupidvar : integer read stupidvariable;
constructor Create;
end;
TSomeClass = class
private
m_Obj : ^TThread_A;
procedure readVar;
public
constructor Create(obj: TThread_A);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
constructor TSomeClass.Create(obj: TThread_A);
begin
m_Obj := #obj;
readVar;
end;
procedure TSomeClass.readVar;
begin
showmessage(inttostr(m_Obj.getstupidvar));
end;
constructor TThread_A.Create;
begin
inherited Create(false);
FreeOnTerminate := True;
end;
procedure TThread_A.Execute;
begin
stupidvariable := 100;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
threadA : TThread_A;
someClass : TSomeClass;
begin
threadA := TThread_A.Create;
someClass := TSomeClass.Create(threadA);
end;
end.
What happens here exactly?
I thought I am passing the object "threadA" to someClass and assign the address of "threadA" to "m_Obj".
Why is the object lost?
constructor TSomeClass.Create(obj: TThread_A);
begin
m_Obj := #obj;
readVar;
end;
Here obj is (in essence) a local variable, and so its lifetime ends when the function returns. Therefore you have remembered the address of something that no longer exists.
In fact you have too much indirection. Because TThread_A is a class, and classes are reference types, it is already a pointer. Change
m_Obj: ^TThread_A;
to
m_Obj: TThread_A;
and
m_Obj := #obj;
to
m_Obj := obj;
Now you are taking a copy of the reference to the instance, which is what I believe that you mean to do.
Not even that will leave you with a working program though. Because you set FreeOnTerminate the thread can be destroyed at any time. That means that you must not hold a reference to it since that reference can become invalid behind your back. So, you should also set FreeOnTerminate to False.
Not even that will leave you with predicatable outcome though. The thread procedure executes independently from the main thread. When you read the variable from the main thread, the thread may, or may not, have modified the variable. This is known as a data race. If you wish to wait until after the variable has been modified then you could use, for instance, an event object to allow the thread to signal that the variable is ready to be read.

Multithread interactions

Main Form:
unit Unit3;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm3 = class(TForm)
Memo1: TMemo;
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
class var counter : Integer;
end;
var
Form3: TForm3;
implementation
{$R *.dfm}
uses Unit4, Unit5;
procedure TForm3.Button1Click(Sender: TObject);
var
th1 : thCounter;
th2 : thPrinter;
begin
th1:= thCounter.Create;
th2:= thPrinter.Create;
end;
end.
Thread Counter :
unit Unit4;
interface
uses
System.Classes, Unit3;
type
thCounter = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
end;
implementation
{ thCounter }
procedure thCounter.Execute;
var
i: Integer;
printVal : Integer;
begin
{ Place thread code here }
printVal:= 50;
for i := 0 to 1000000000 do
begin
Form3.counter:= i;
if Form3.counter = printVal then
begin
// RUN print thread ????
printVal:= printVal + 50;
end;
end;
end;
end.
Thread Print :
unit Unit5;
interface
uses
System.Classes, Unit3;
type
thPrinter = class(TThread)
private
{ Private declarations }
procedure printIt;
protected
procedure Execute; override;
end;
implementation
uses
System.SysUtils;
{ thPrinter }
procedure thPrinter.Execute;
begin
{ Place thread code here }
Synchronize(printIt);
end;
procedure thPrinter.printIt;
begin
Form3.Memo1.Lines.Add(IntToStr(Form3.counter));
end;
end.
I am workin on simple project. But i stuck.
I have 2 thread which are thCounter and thPrint.
My thCounter, increase the Counter to 1 billion. And i want to call another thread ( thPrint ) when the counter 50 and multiples like 100, 150, 200 to print the screen in TMemo....
How can i send a message to thPrint from thCounter?
To signal the other thread, use a synchronization primitive, like a TSimpleEvent.
Let it be owned by the thCounter thread, and pass a reference to it when thPrinter is created.
In the thPrinter.Execute:
while not Terminated do
begin
if waitEvent.WaitFor(100) = wrSignaled then // Keep it listening to the Terminated flag
begin
Synchronize(PrintIt);
end;
end;
And in the thCounter:
waitEvent.SetEvent; // Triggers the printIt
Just create the waitEvent so that it automatically is reset after the event has been triggered.
waitEvent := TSimpleEvent.Create(nil, false,false,'');

Execute thread on some object property change

I make a logging application and I have a LogEvent object with some string properties on it. I want to make this logging asynchronous and in another thread for not blocking the applications GUI thread.
Idea is that when I start application, some LogEventThread is running on the background all the time. If LogEvent property has changed then thread is executed, after execution thread suspends and waits another LogEvent object property change and run it again if new property change is captured.
Which are the best practises to design this?
EDIT:
I created an example. Please tell me if I'm on the correct path.
I have a Form1:
unit MainWindow;
interface
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, TrackEventSenderThread, Generics.Collections, TrackEvent;
type
TForm1 = class(TForm)
btnTest: TButton;
procedure FormCreate(Sender: TObject);
procedure btnTestClick(Sender: TObject);
private
teqTrackEventSenderThread: TTrackEventSenderThread;
trackEventQueue: TThreadedQueue<TTrackEvent>;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.btnTestClick(Sender: TObject);
var
trackEvent: TTrackEvent;
begin
trackEvent := TTrackEvent.Create;
trackEvent.Category := 'test';
trackEvent.Action := 'test';
trackEventQueue.PushItem(trackEvent);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
trackEventQueue := TThreadedQueue<TTrackEvent>.Create;
teqTrackEventSenderThread := TTrackEventSenderThread.Create(True);
teqTrackEventSenderThread.TrackEventQueue := trackEventQueue;
teqTrackEventSenderThread.Start;
end;
end.
TrackEvent class:
unit TrackEvent;
interface
type
TTrackEvent = class(TObject)
private
sCategory: string;
sAction: string;
public
property Category: string read sCategory write sCategory;
property Action: string read sAction write sAction;
end;
implementation
end.
And thread class:
unit TrackEventSenderThread;
interface
uses Classes, Generics.Collections, TrackEvent;
type
TTrackEventSenderThread = class(TThread)
private
trackEvent: TTrackEvent;
teqTrackEventQueue: TThreadedQueue<TTrackEvent>;
public
constructor Create(CreateSuspended: Boolean);
property TrackEventQueue: TThreadedQueue<TTrackEvent> read teqTrackEventQueue write teqTrackEventQueue;
protected
procedure Execute; override;
end;
implementation
constructor TTrackEventSenderThread.Create(CreateSuspended: Boolean);
begin
inherited;
end;
procedure TTrackEventSenderThread.Execute;
begin
while not Terminated do
begin
if teqTrackEventQueue.QueueSize > 0 then
begin
trackEvent := teqTrackEventQueue.PopItem;
//send data to server
end;
end;
end;
end.
You can build a thread-safe Queue class which is used in a Producer-Consumer model. Your TThread descendant class should own an instance of this Queue class.
When you start your application, your queue is empty, and your logging thread is blocked waiting for queue. When you push a new string into the queue from the main thread, your queue pulses the logging thread, your logging thread wakes up and pops items from the queue until the queue is empty again.
To implement the queue in Delphi 2010, you can use TQueue generic class as the base type, and use System.TMonitor for synchronization. In Delphi XE, there is already a class which implements this for you, named TThreadedQueue. So If you are using Delphi XE, create an instance of TThreadedQueue, and in your logging thread try to call its PopItem() method.
EDIT:
Here is a sample logging thread which receives string logs:
unit uLoggingThread;
interface
uses
SysUtils, Classes, Generics.Collections, SyncObjs {$IFDEF MSWINDOWS} , Windows {$ENDIF};
type
TLoggingThread = class(TThread)
private
FFileName : string;
FLogQueue : TThreadedQueue<string>;
protected
procedure Execute; override;
public
constructor Create(const FileName: string);
destructor Destroy; override;
property LogQueue: TThreadedQueue<string> read FLogQueue;
end;
implementation
{ TLoggingThread }
constructor TLoggingThread.Create(const FileName: string);
begin
inherited Create(False);
FFileName := FileName;
FLogQueue := TThreadedQueue<string>.Create;
end;
destructor TLoggingThread.Destroy;
begin
FLogQueue.Free;
inherited;
end;
procedure TLoggingThread.Execute;
var
LogFile : TFileStream;
FileMode : Word;
ALog : string;
begin
NameThreadForDebugging('Logging Thread');
// FreeOnTerminate := True;
if FileExists(FFileName) then
FileMode := fmOpenWrite or fmShareDenyWrite
else
FileMode := fmCreate or fmShareDenyWrite;
LogFile := TFileStream.Create(FFileName,FileMode);
try
while not Terminated do
begin
ALog := FLogQueue.PopItem;
if (ALog <> '') then
LogFile.Write(ALog[1],Length(ALog)*SizeOf(Char));
end;
finally
LogFile.Free;
end;
end;
end.
This TThread descendant uses a TThreadedQueue object as a buffer. When FLogQueue.PopItem is called, if the queue is empty, the thread goes to sleep, and waits until something is pushed into the queue. When an item is available in the queue, the thread pops it, and writes it to a file. This is a very simple code to just let you understand the basics of what you should do.
And here is a sample code for a form which is running in the context of main thread, and is logging a sample message:
unit fMain;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, uLogginThread;
type
TfrmMain = class(TForm)
btnAddLog: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure btnAddLogClick(Sender: TObject);
private
FLoggingThread : TLoggingThread;
public
{ Public declarations }
end;
var
frmMain: TfrmMain;
implementation
{$R *.dfm}
procedure TfrmMain.FormCreate(Sender: TObject);
begin
FLoggingThread := TLoggingThread.Create(ExtractFilePath(Application.ExeName) + 'Logs.txt');
end;
procedure TfrmMain.FormDestroy(Sender: TObject);
begin
FLoggingThread.Terminate;
FLoggingThread.LogQueue.DoShutDown;
FLoggingThread.WaitFor;
FreeAndNil(FLoggingThread);
end;
procedure TfrmMain.btnAddLogClick(Sender: TObject);
begin
FLoggingThread.LogQueue.PushItem('This is a test log. ');
end;
end.
Here an instance of TLoggingThread is created when the form is initialized. When you press btnAddLog, a sample message is sent to the logger thread via its LogQueue property.
Take note of how the thread is terminated in FormDestroy method. First the thread is signaled that it is terminated, then we tell LogQueue to release any lock, so if the logger thread is waiting for the queue, it will wake up automatically after calling DoShutDown. Then we wait for the thread to finish up by calling WaitFor method, and eventually we destroy the thread instance.
Good Luck
In a multi-threaded application, use
TEvent to allow one thread to signal
to other threads that an event has
occurred.
http://docwiki.embarcadero.com/VCL/en/SyncObjs.TEvent
I would use a Queue of strings with a critical section inside push() and pop(). Inside the thread I would pop strings off, and log them. Inside the GUI thread I would push strings on the queue. I have done something similar before, and it is simple to implement.
Edit
Interface:
TThreadSafeQueue = class(TQueue)
protected
procedure PushItem(AItem: Pointer); override;
function PopItem: Pointer; override;
function PeekItem: Pointer; override;
end;
var
CRITICAL_SECTION: TCriticalSection;
Implementation:
function TThreadSafeQueue.PeekItem: Pointer;
begin
CRITICAL_SECTION.Enter;
Result := inherited PeekItem;
CRITICAL_SECTION.Leave;
end;
function TThreadSafeQueue.PopItem: Pointer;
begin
CRITICAL_SECTION.Enter;
Result := inherited PopItem;
CRITICAL_SECTION.Leave;
end;
procedure TThreadSafeQueue.PushItem(AItem: Pointer);
begin
CRITICAL_SECTION.Enter;
inherited PushItem(AItem);
CRITICAL_SECTION.Leave;
end;
Initialization
CRITICAL_SECTION := TCriticalSection.Create;
Finalization
FreeAndNil(CRITICAL_SECTION);
This code uses pointers to objects, but you can create storage for your strings inside the object, using a stringlist or array or whatever best fits your purpose, and change the pop and push methods to operate on your own storage.
Edit
Something like this:
procedure TMyThread.Execute;
var
Msg: string;
begin
while not Terminated do
begin
if FQueue.Count > 0 then
begin
Msg := FQueue.pop();
PerformLog(Msg); {Whatever your logging method is}
end;
Sleep(0);
end;
end;

Resources