I need to do a label to blink 5 times using a thread.
When I click on the button, I need the label blinks 5 times.
Now, I have a problem.
when I close the form I have a Memory Leak on Thread.
What am I doing wrong here?
type
TForm1= class(TForm)
...
labelNewMsg:Tlabel;
private
MEvent: TEvent;
procedure Torm1.FormCreate(Sender: TObject);
begin
MEvent := TEvent.Create(nil, False, False, '');
waitNewMessage();
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
MEvent.Free;
end;
procedure TForm1.ButtonDoSetEventClick(Sender: TObject);
begin
Mevent.SetEvent;
end;
procedure TForm1.waitNewMessage;
var
Status:TWaitResult;
begin
TThread.CreateAnonymousThread(
procedure
var IntCnt: Integer;
begin
while not TThread.CurrentThread.CheckTerminated and (not application.terminated) do begin
Sleep(100);
Status:=MEvent.WaitFor(INFINITE);
if Status=wrSignaled then begin
for IntCnt:=1 to 5 do begin
Sleep(1000);
TThread.Synchronize(nil,procedure begin
labelNewMsg.Visible:=not labelNewMsg.Visible;
end);
end;
IntCnt:=0;
MEvent.ResetEvent;
end;
end;
end
).Start;
end;
Hi, I created a second option, but I have the same problem:
procedure TFrm_PrincipalDemo.waitNewMessage;
var
Status:TWaitResult;
begin
TThread.CreateAnonymousThread(
procedure
var IntCnt: Integer;
begin
while MEvent.WaitFor(INFINITE) in [wrSignaled] do begin
if TThread.CurrentThread.CheckTerminated then exit;
MEvent.ResetEvent;
Sleep(100);
for IntCnt:=1 to 5 do begin
Sleep(1000);
TThread.Synchronize(nil,procedure begin
labelNewMsg.Visible:=not labelNewMsg.Visible;
end);
end;
if TThread.CurrentThread.CheckTerminated then exit;
end;
end
).Start;
end;
You are not signaling the thread to terminate itself before your Form is closed. For instance, if the thread is blocked waiting for MEvent, you need to signal MEvent so the thread can wake up and check for termination.
The Application.Terminated property is not set to True until the main message loop has processed a WM_QUIT message from PostQuitMessage(), which Application.Terminate() calls. The program's Application.MainForm calls Application.Terminate() when the Form is closed (not destroyed, that comes later).
If you keep a reference to the TThread object that you create, you can then call the TThread.Terminate() method directly, which sets the thread's Terminated property to True (otherwise, there is no point in calling TThread.CheckTerminated() inside of the thread at all), eg:
type
TForm1 = class(TForm)
...
labelNewMsg: TLabel;
ButtonDoSetEvent: TButton;
...
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; Action: TCloseAction);
procedure FormDestroy(Sender: TObject);
procedure ButtonDoSetEventClick(Sender: TObject);
...
private
MEvent: TEvent;
Thread: TThread;
procedure waitNewMessage;
procedure ThreadTerminated(Sender: TObject);
...
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
MEvent := TEvent.Create(nil, False, False, '');
waitNewMessage();
end;
procedure TForm1.FormClose(Sender: TObject; Action: TCloseAction);
begin
if Thread <> nil then
begin
Thread.Terminate;
MEvent.SetEvent;
end;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if Thread <> nil then
Thread.OnTerminate := nil;
MEvent.Free;
end;
procedure TForm1.ButtonDoSetEventClick(Sender: TObject);
begin
MEvent.SetEvent;
end;
procedure TForm1.waitNewMessage;
begin
Thread := TThread.CreateAnonymousThread(
procedure
var
IntCnt: Integer;
Status: TWaitResult;
begin
while not TThread.CheckTerminated do begin
Sleep(100);
Status := MEvent.WaitFor(INFINITE);
if (Status = wrSignaled) and (not TThread.CheckTerminated) then begin
for IntCnt := 1 to 5 do begin
Sleep(1000);
TThread.Synchronize(nil,
procedure
begin
labelNewMsg.Visible := not labelNewMsg.Visible;
end
);
end;
end;
end;
end
);
Thread.OnTerminate := ThreadTerminated;
Thread.Start;
end;
procedure TForm1.ThreadTerminated(Sender: TObject);
begin
Thread := nil;
end;
But really, why are you even using a thread at all? Nothing your thread does actually needs to be in a thread in the first place. A simple timer would suffice instead, and it would be safer for the UI, and easier to stop during program shutdown.
type
TForm1 = class(TForm)
...
labelNewMsg: TLabel;
ButtonDoSetEvent: TButton;
NewMsgTimer: TTimer;
...
procedure ButtonDoSetEventClick(Sender: TObject);
procedure NewMsgTimerTimer(Sender: TObject);
...
end;
procedure TForm1.ButtonDoSetEventClick(Sender: TObject);
begin
NewMsgTimer.Tag := 0;
NewMsgTimer.Enabled := True;
end;
procedure TForm1.NewMsgTimerTimer(Sender: TObject);
begin
NewMsgTimer.Tag := NewMsgTimer.Tag + 1;
labelNewMsg.Visible := not labelNewMsg.Visible;
if NewMsgTimer.Tag = 5 then
NewMsgTimer.Enabled := False;
end;
I have a TTimer on a TForm, where the timer is set to 5 seconds and creates 100 threads to fetch XML from a remote server.
Each time a thread is executed, I add the XML to a variable (FullXML_STR:String).
When all threads have finished, I am sending the FullXML_STR to all Clients connected to a TIdTCPServer.
unit Unit1;
interface
uses
IdGlobal,IdContext, system.win.Comobj, system.syncObjs, MSXML2_TLB, activex,
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, IdBaseComponent, IdComponent,
IdTCPConnection, IdTCPClient, IdHTTP, IdCustomTCPServer, IdCustomHTTPServer,
IdHTTPServer, Vcl.ExtCtrls;
Type
TxClientThread = class(TThread)
private
fHttpClient: TIdHTTP;
furl: String;
ftag:Integer;
fResponseXML:String;
fXML: IXMLDOMDocument;
fNode: IXMLDomNode;
protected
procedure Execute; override;
procedure DoTerminate; override; **//Added**
public
constructor Create(atag:Integer;AURL:string);reintroduce;
destructor Destroy; override;
end;
type
TForm1 = class(TForm)
IdTCPServer1: TIdHTTPServer;
Timer1: TTimer;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
procedure StartTimerAgain;
end;
const
maximumThreads=200;
var
Form1: TForm1;
Threads_downloaded:Integer;
Total_threads:Integer;
FullXML_STR:String;
Clients:TList;
CriticalSection:TCriticalSection;
ClientThread:Array[0..maximumThreads] of TxClientThread;
implementation
{$R *.dfm}
{TxClientThread}
constructor TxClientThread.Create(atag:Integer;AURL:string);
begin
inherited Create(false);
furl:=Aurl;
ftag:=Atag;
fResponseXML:='';
fHttpClient := TIdHTTP.Create(nil);
fHttpClient.Tag:=ftag;
fHttpClient.ConnectTimeout:=60000;
fHttpClient.ReadTimeout:=60000;
fHttpClient.Request.Accept:='*/*';
fHttpClient.Request.UserAgent:='Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36';
FreeOnTerminate := True;
end;
destructor TxClientThread.Destroy;
begin
fHttpClient.Free;
inherited Destroy;
end;
procedure TxClientThread.Execute;
begin
try
fResponseXML:= fHttpClient.Get(furl);
except
end;
end;
procedure TxClientThread.DoTerminate;
begin
inc(Threads_downloaded);
///****** parsing The XML
try
CoInitialize(nil);
fXML := CreateOleObject('Microsoft.XMLDOM') as IXMLDOMDocument;
fXML.async := false;
try
fXML.loadXML(fResponseXML);
fNode := fXML.selectSingleNode('/games');
if fNode<>nil then
begin
FullXML_STR:=FullXML_STR + fNode.attributes.getNamedItem('id').text+'^';
end;
finally
fxml:=nil; //---> do i need this?
end;
finally
CoUninitialize;
end;
if Threads_downloaded=Total_threads then
begin
TThread.Synchronize(nil,procedure/////////Sould i USe This or Synchronize
var
i:Integer;
begin
CriticalSection.enter;
if not Assigned(Form1.IdTCPServer1.Contexts) then exit;
try
Clients:=Form1.IdTCPServer1.Contexts.LockList;
try
for i:=pred(Clients.Count) downto 0 do
try
TIdContext(Clients[i]).Connection.IOHandler.Writeln(FullXML_STR,IndyTextEncoding_UTF8);
except
end;
finally
Form1.IdTCPServer1.Contexts.UnlockList;
end;
finally
CriticalSection.leave;
end;
form1.StartTimerAgain; ///Startinmg againe Then timer
end
);
end;
/////////// End \ All threads downloaded
inherited;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
CriticalSection:=TCriticalSection.create;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
CriticalSection.Free;
end;
procedure tform1.StartTimerAgain;
begin
Form1.Timer1.Enabled:=true
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
x:Integer;
aUrl:String;
begin
FullXML_STR:='';
Timer1.Enabled:=false;
Threads_downloaded:=0;
Total_threads=100;
for x:=0 to Pred(Total_threads) do
begin
aUrl:='http://example.com/myxml'+Formatfloat('0',x)+'.xml';
ClientThread[Threads_downloaded]:=TxClientThread.Create(x,aUrl);
end;
end;
end.
main problem is that after 1-2 Hours programm is not responding.
in each thread's Execute(), I check if all Threads have finished downloading. Is there a better way to know that all my threads are finished?
is it better to call Contexts.LockList() on the TIdTCPServer before the timer starts creating the threads, and unlock it after the threads are finished?
What can I do to optimize my code so I can be sure that the timer will be alive all the time? I am restarting the timer after all threads are finished.
Is this the correct way to do it?
Request:
How is it possible to accept a string like hi from a client connected on the TIdTCPServer and send back a string.
I try to add the following code:
var
RxBuf: TIdBytes;
Data := TxClientContext(AContext).ExtractQueuedStrings;
if Data <> nil then
try
for i := 0 to Pred(Data.Count) do
AContext.Connection.IOHandler.WriteLn(Data[i]);
finally
Data.Free;
end;
RxBuf := nil;
with AContext.Connection do
begin
IOHandler.CheckForDataOnSource(100);
if not IOHandler.InputBufferIsEmpty then
begin
InputBuffer.ExtractToBytes(RxBuf); //for TIdBytes
AContext.Connection.IOHandler.WriteLn('hello');
end;
end;
After sending hello the app never sends data from the queue.
How can I add the hello to Data extract from queue?
Something like this:
Data := TxClientContext(AContext).ExtractQueuedStrings;
and then
data.text:=data.text +'hello data';
or how can I add the 'hello data' in the queue?
I see a lot of mistakes in your code. Rather than pointing them out individually, I would suggest just rewritting the entire code, especially since you are also asking for optimizations.
Try something more like this instead:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls,
IdGlobal, IdContext, IdBaseComponent, IdComponent, IdTCPConnection, IdCustomTCPServer,
IdTCPServer, IdThreadSafe;
type
TIdTCPServer = class(IdTCPServer.TIdTCPServer)
protected
procedure DoTerminateContext(AContext: TIdContext); override;
end;
TForm1 = class(TForm)
IdTCPServer1: TIdTCPServer;
Timer1: TTimer;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure IdTCPServer1Connect(AContext: TIdContext);
procedure IdTCPServer1Execute(AContext: TIdContext);
private
{ Private declarations }
IDs: TIdThreadSafeString;
Threads: TList;
procedure ThreadTerminated(Sender: TObject);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses
System.Win.Comobj, MSXML2_TLB, ActiveX, System.SyncObjs, IdHTTP, IdYarn;
{$R *.dfm}
const
maximumThreads = 100;//200;
{TxClientContext}
type
TxClientContext = class(TIdServerContext)
private
fQueue: TIdThreadSafeStringList;
fInQueue: TEvent;
public
constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil); override;
destructor Destroy; override;
procedure AddStringToQueue(const S: string);
function ExtractQueuedStrings: TStrings;
end;
constructor TxClientContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil);
begin
inherited;
fQueue := TIdThreadSafeStringList.Create;
fInQueue := TEvent.Create(nil, True, False, '');
end;
destructor TxClientContext.Destroy; override;
begin
fQueue.Free;
fInQueue.Free;
inherited;
end;
procedure TxClientContext.AddStringToQueue(const S: string);
var
List: TStringList;
begin
List := fQueue.Lock;
try
List.Add(S);
fInQueue.SetEvent;
finally
fQueue.Unlock;
end;
end;
function TxClientContext.ExtractQueuedStrings: TStrings;
var
List: TStringList;
begin
Result := nil;
if fInQueue.WaitFor(INFINITE) <> wrSignaled then Exit;
List := FQueue.Lock;
try
if List.Count > 0 then
begin
Result := TStringList.Create;
try
Result.Assign(List);
List.Clear;
except
Result.Free;
raise;
end;
end;
fInQueue.ResetEvent;
finally
fQueue.Unlock;
end;
end;
{TxClientThread}
type
TxClientThread = class(TThread)
private
fURL: String;
protected
procedure Execute; override;
public
GameID: string;
constructor Create(AURL: string; AOnTerminate: TNotifyEvent); reintroduce;
end;
constructor TxClientThread.Create(AURL: string; AOnTerminate: TNotifyEvent);
begin
inherited Create(False);
fURL := AURL;
OnTerminate := AOnTerminate;
FreeOnTerminate := True;
end;
procedure TxClientThread.Execute;
var
HttpClient: TIdHTTP;
ResponseXML: String;
XML: IXMLDOMDocument;
Node: IXMLDomNode;
begin
HttpClient := TIdHTTP.Create(nil);
try
HttpClient.ConnectTimeout := 60000;
HttpClient.ReadTimeout := 60000;
HttpClient.Request.Accept := '*/*';
HttpClient.Request.UserAgent := 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36';
ResponseXML := HttpClient.Get(fURL);
finally
HttpClient.Free;
end;
CoInitialize(nil);
try
XML := CreateOleObject('Microsoft.XMLDOM') as IXMLDOMDocument;
try
XML.async := False;
XML.loadXML(ResponseXML);
Node := XML.selectSingleNode('/games');
if Node <> nil then
try
GameID := Node.attributes.getNamedItem('id').text;
finally
Node := nil;
end;
finally
XML := nil;
end;
finally
CoUninitialize;
end;
end;
{TIdTCPServer}
procedure TIdTCPServer.DoTerminateContext(AContext: TIdContext);
begin
inherited; // <-- closes the socket
TxClientContext(AContext).FInQueue.SetEvent; // unblock OnExecute if it is waiting for data...
end;
{TForm1}
procedure TForm1.FormCreate(Sender: TObject);
begin
IdTCPServer1.ContextClass := TxClientContext;
IDs := TIdThreadSafeString.Create;
Threads := TList.Create;
Threads.Capacity := maximumThreads;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
IDs.Free;
Threads.Free;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
x: Integer;
Thread: TxClientThread;
begin
Timer1.Enabled := False;
IDs.Value := '';
for x := 0 to Pred(maximumThreads) do
begin
Thread := TxClientThread.Create('http://example.com/myxml' + IntToStr(x) + '.xml', ThreadTerminated);
try
Threads.Add(TObject(Thread));
except
Thread.Free;
raise;
end;
end;
end;
proccedure TForm1.ThreadTerminated(Sender: TObject);
var
Clients: TList;
s: string;
i: Integer;
begin
try
s := TxClientThread(Sender).GameID;
if s <> '' then IDs.Append(s + '^');
finally
Threads.Remove(Sender);
end;
if (Threads.Count > 0) or (not Assigned(IdTCPServer1.Contexts)) then Exit;
s := IDs.Value;
if s = '' then Exit;
Clients := IdTCPServer1.Contexts.LockList;
try
for i := Pred(Clients.Count) downto 0 do
try
TxClientContext(TIdContext(Clients[i])).AddStringToQueue(s);
except
end;
finally
IdTCPServer1.Contexts.UnlockList;
end;
Timer1.Enabled := True;
end;
procedure TForm1.IdTCPServer1Connect(AContext: TIdContext);
begin
AContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
end;
procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
Data: TStrings;
i: Integer;
begin
Data := TxClientContext(AContext).ExtractQueuedStrings;
if Data <> nil then
try
for i := 0 to Pred(Data.Count) do
AContext.Connection.IOHandler.WriteLn(Data[i]);
finally
Data.Free;
end;
end;
end.
In each thread, you add the resulting string into a global variable. That is not a safe operation. Instead, add an OnTerminate handler to your threads, where you add the result and also can keep track of the threads.
This is safe, since the OnTerminate handler is executed in the main thread.
I suggest to pass a callback method to pass the result. It is declared like:
type
TSyncMethod = procedure(const ReturnValue: String) of object;
Change the thread accordingly:
Type
TxClientThread = class(TThread)
private
furl : String;
ftag : Integer;
fCallbackMethod : TSyncMethod;
fXMLResult : String;
procedure AfterWork(Sender : TObject);
...
public
constructor Create(atag: Integer; AURL: string; CallbackMethod : TSyncMethod); reintroduce;
...
end;
Add a callback method to your form:
Type
TForm1 = Class(TForm1)
private
// Put your "global" variables here
Threads_downloaded : Integer;
Total_threads : Integer;
FullXML_STR : String;
procedure ManageThreadReturnValue(const ReturnValue : String); // Callback from threads
...
end;
The implementation part:
constructor TxClientThread.Create(atag: Integer; AURL: string; CallbackMethod : TSyncMethod);
begin
inherited Create(false);
furl := Aurl;
ftag := Atag;
fCallbackMethod := CallbackMethod;
fXMLResult := '';
OnTerminate := AfterWork; // Execute AfterWork when thread terminates (in main thread)
FreeOnTerminate := True;
end;
procedure TxClientThread.Execute;
var
lHttpClient : TIdHTTP;
lResponseXML :String;
lXML : IXMLDOMDocument;
lNode : IXMLDomNode;
begin
lHttpClient := TIdHTTP.Create(nil);
try
lHttpClient.Tag := ftag;
lHttpClient.ConnectTimeout := 60000;
lHttpClient.ReadTimeout := 60000;
lHttpClient.Request.Accept := '*/*';
lHttpClient.Request.UserAgent :=
'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36';
try
lResponseXML:= lHttpClient.Get(fUrl);
except
end;
finally
lHttpClient.Free;
end;
///****** parsing The XML
CoInitialize(nil);
try
lXML := CreateOleObject('Microsoft.XMLDOM') as IXMLDOMDocument;
lXML.async := false;
try
lXML.loadXML(lResponseXML);
lNode := lXML.selectSingleNode('/games');
if lNode<>nil then
begin
fXMLResult := lNode.attributes.getNamedItem('id').text+'^';
end;
finally
lnode := nil;
lxml := nil; //---> Q: do i need this?
//---> A: Yes, it must be finalized before CoUnitialize
end;
finally
CoUninitialize;
end;
end;
procedure TxClientThread.AfterWork;
begin
if Assigned(fCallbackMethod) then
fCallbackMethod(fXMLResult); // Pass data
end;
procedure TForm1.ManageThreadReturnValue(const ReturnValue : String);
var
i : Integer;
Clients : TList;
begin
// Take care of the return value and other things related to
// what happens when a thread ends.
FullXML_STR := FullXML_STR + ReturnValue;
Inc(threads_downloaded);
if Threads_downloaded = Total_threads then
begin
if Assigned(IdTCPServer1.Contexts) then
begin
Clients:= IdTCPServer1.Contexts.LockList;
try
for i:= Pred(Clients.Count) downto 0 do
begin
try
TIdContext(Clients[i]).Connection.IOHandler.Writeln(
FullXML_STR,IndyTextEncoding_UTF8);
except
end;
end;
finally
IdTCPServer1.Contexts.UnlockList;
end;
end;
StartTimerAgain; ///Starting again The timer
end;
end;
// Initiate threads
FullXML_STR:='';
Timer1.Enabled:=false;
Threads_downloaded:=0;
Total_threads=100;
for x:= 0 to Pred(Total_threads) do
begin
aUrl:='http://example.com/myxml'+Formatfloat('0',x)+'.xml';
TxClientThread.Create(x,aUrl,ManageThreadReturnValue); // !! Never keep a reference to a thread with FreeOnTerminate = true
end;
Some other hints:
Put your global variables into the private section of TForm1. This is the place where they belong.
Remove the ClientThread array, since a reference to a thread with FreeOnTerminate = true should never be used.
Do not swallow exceptions, i.e. empty except end clauses are not a good practice.
By using the callback method, you decouple the thread from code/data that does not belong to the thread. That is one of the most important lessons to learn when programming (i.e. avoid making spaghetti code).
I have a small Windows software created with Delphi 7 where a thread periodically do some action, like save information in a SQLite database. It works just fine, but the same thread never execute when Windows is about to shutdown/reboot/logoff. Here is a simple example:
type
TSaveText = class(TThread)
private
FText: string;
protected
procedure Execute; override;
end;
...
private
procedure WMQueryEndSession(var AMsg: TMessage); message WM_QUERYENDSESSION;
procedure SaveText(const AText: string);
...
procedure AddToLog(const Str: string);
var
Pth: string;
Txt: TextFile;
begin
Pth := ExtractFilePath(ParamStr(0)) + 'log.txt';
try
AssignFile(Txt, Pth);
if not FileExists(Pth) then
ReWrite(Txt);
Append(Txt);
WriteLn(Txt, Trim(Str));
finally
CloseFile(Txt);
end;
end;
procedure TfrmMain.SaveText(const AText: String);
begin
with TSaveText.Create(True) do
begin
FText := AText;
FreeOnTerminate := True;
Priority := tpNormal;
Resume;
end;
end;
procedure TSaveText.Execute;
begin
inherited;
AddToLog(FText);
end;
procedure TfrmMain.WMQueryEndSession(var AMsg: TMessage);
begin
inherited;
SaveText('Windows is about to shutdown/reboot/logoff!');
AMsg.Result := 1;
end;
In this example, the text 'Windows is about to shutdown/reboot/logoff!' is never saved in the log file. But if I remove the action from the thread, it works:
procedure TfrmMain.WMQueryEndSession(var AMsg: TMessage);
begin
inherited;
AddToLog('Windows is about to shutdown/reboot/logoff!');
AMsg.Result := 1;
end;
I'd like to know if there is a way to force thread to execute in this scenario, when Windows is about to shutdown/reboot/logoff.
Thanks!
I have a TMyIdHTTPServer server object extends TIdHTTPServer. My TMyIdHTTPServer has some private variable shared in a multithreading context (i think TIdHTTPServer creates a new thread for each request).
I'm not a delphi or multithreading programming expert and i'm not sure that my approach is safe and/or has good performance.
What happens when some threads read same variable, there is performance degradation? there is a risk for thread conflict?
Basic example:
type
TMyLogObject = class
// write string LogMessage in LogFilePath
procedure WriteLog(const LogMessage, LogFilePath: string);
end;
TMyIdHTTPServer = class(TIdHTTPServer)
private
FMyLogObject : TMyLogObject;
FLogPath : string;
procedure ServerStart;
procedure ServerStop;
procedure OnCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
procedure OnWorkEnd(ASender: TObject; AWorkMode: TWorkMode);
public
constructor Create(const ConfigINI string); reintroduce;
end;
implementation
// ConfigINI is a absolute path of server_config.ini
constructor Create(const ConfigINI string);
var
aConfigINI: TIniFile;
begin
inherited;
aConfigINI := TIniFile.Create(ConfigINI);
try
// set the path of server log file
FLogPath := FConfigINI.ReadString('CONFIG', 'LOG_PATH', '');
finally
aConfigINI.free;
end;
end;
procedure TMyIdHTTPServer.ServerStart;
begin
self.Active := True;
FMyLogObject := TMyLogObject.Create;
end;
procedure TMyIdHTTPServer.ServerStop;
begin
self.Active := False;
FMyLogObject.Free;
end;
procedure TMyIdHTTPServer.OnCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
AContext.Connection.OnWorkEnd := OnWorkEnd;
FMyLogObject.WriteLog('StartRequest', FLogPath + 'log.txt');
AResponseInfo.ContentText := 'Hello!';
end;
procedure TMyIdHTTPServer.OnWorkEnd(ASender: TObject; AWorkMode: TWorkMode);
begin
FMyLogObject.WriteLog('EndRequest', FLogPath + 'log.txt');
end;
I'm trying to put an indy TIdHttp in a thread,
I have tried this :
type
TSendThread = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
public
http : TIdHTTP;
URL : String;
Method : String;
property ReturnValue;
end;
procedure TSendThread.Execute;
begin
form1.Memo1.lines.Add(http.Get(URL));
ReturnValue := 1;
end;
And in the main :
procedure TForm1.Button1Click(Sender: TObject);
var t : TSendThread;
begin
t := TSendThread.Create(true);
t.URL := 'http://www.url.com/';
t.http := http;
t.Start;
showmessage(IntToStr(t.ReturnValue));
end;
My problem here is that the next instruction gets executed(showmessage) without waiting the thread to be done, i tried to use the "WaitFor" but it freezes the application.
Is there any other workaround?
Thank you.
Use the TThread.OnTerminate event to know when the thread has finished:
type
TSendThread = class(TThread)
private
http : TIdHTTP;
Line: string;
procedure AddLine;
protected
procedure Execute; override;
public
constructor Create; reintroduce;
destructor Destroy; override;
URL : String;
Method : String;
property ReturnValue;
end;
constructor TSendThread.Create;
begin
inherited Create(True);
FreeOnTerminate := True;
http := TIdHTTP.Create;
end;
destructor TSendThread.Destroy;
begin
http.Free;
inherited;
end;
procedure TSendThread.Execute;
begin
Line := http.Get(URL);
Synchronize(AddLine);
ReturnValue := 1;
end;
procedure TSendThread.AddLine;
begin
Form1.Memo1.Lines.Add(Line);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
t : TSendThread;
begin
t := TSendThread.Create;
t.URL := 'http://www.url.com/';
t.OnTerminate := ThreadTerminated;
t.Start;
end;
procedure TForm1.ThreadTerminated(Sender: TObject);
begin
ShowMessage(IntToStr(TSendThread(Sender).ReturnValue));
end;
If you want to use a loop to wait for the thread to finish, without blocking the UI, then you can do it like this:
constructor TSendThread.Create;
begin
inherited Create(True);
//FreeOnTerminate := True; // <-- remove this
http := TIdHTTP.Create;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
t : TSendThread;
h : THandle;
begin
t := TSendThread.Create;
try
t.URL := 'http://www.url.com/';
t.Start;
h := t.Handle;
repeat
case MsgWaitForMultipleObjects(1, h, 0, INFINITE, QS_ALLINPUT) of
WAIT_OBJECT_0: Break;
WAIT_OBJECT_0+1: Application.ProcessMessages;
WAIT_FAILED: RaiseLastOSError;
else
Break;
end;
until False;
ShowMessage(IntToStr(t.ReturnValue));
finally
t.Free;
end;
end;