i have this TThread that i use inside my dll to update some visual control its working fine but i face issue when i try to close my dll and reopen it again its raised this exception
checksynchronize called from thread which is not the main thread
what iam doing wrong ? i need to call checksynchronize within timer because i will update some vcl with Threading while app running .
Here is my Thread unit
unit Thread;
interface
uses Messages, Windows, SysUtils, dialogs, Classes, Menus, forms, ComOBJ,
ShlObj;
{ Thread client }
type
TThreadCallbackProc = procedure(Sender: TObject; Updatestring : string) of object;
TAPPTHREAD = class(TThread)
private
Fstatus : String;
FOnCallbackProc: TThreadCallbackProc;
procedure dosomework;
procedure DoCallbackProc;
//
protected
procedure Execute; override;
Public
constructor Create(CreateSuspended: Boolean; aThreadCallbackProc: TThreadCallbackProc);
destructor Destroy; override;
end;
var
APPTHREAD : TAPPTHREAD;
implementation
constructor TAPPTHREAD.Create(CreateSuspended: Boolean;
aThreadCallbackProc: TThreadCallbackProc);
begin
inherited Create(CreateSuspended);
FreeOnTerminate := True;
FOnCallbackProc := aThreadCallbackProc;
end;
destructor TAPPTHREAD.Destroy;
begin
//
end;
procedure TAPPTHREAD.DoCallbackProc;
begin
if Assigned(FOnCallbackProc) then
FOnCallbackProc(self, Fstatus);
end;
procedure TAPPTHREAD.Execute;
begin
while not Terminated do
begin
Fstatus := 'Synched';
if Fstatus <> '' then
dosomework;
end;
end;
procedure TAPPTHREAD.dosomework;
begin
if Assigned(FOnCallbackProc) then
begin
Synchronize(DoCallbackProc);
end;
end;
end.
Main Form
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.ExtCtrls;
type
TForm1 = class(TForm)
Label1: TLabel;
Timer1: TTimer;
Timer2: TTimer;
procedure FormShow(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Timer2Timer(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
public
{ Public declarations }
procedure callbackproc(Sender: TObject; Updatestring : String);
end;
var
Form1: TForm1;
implementation
uses Thread;
{$R *.dfm}
procedure TForm1.callbackproc(Sender: TObject; Updatestring: String);
begin
label1.Caption := updatestring;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := Cafree;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
try
if Assigned(APPTHREAD) then
AppThread.Terminate;
except end;
try
Timer2.Enabled := False;
except end;
end;
procedure TForm1.FormShow(Sender: TObject);
begin
Timer1.Enabled := True;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled := False;
APPTHREAD := TAPPTHREAD.Create(false, CallbackProc);
Timer2.Enabled := True;
end;
procedure TForm1.Timer2Timer(Sender: TObject);
begin
Checksynchronize;
end;
end.
DFM
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 242
ClientWidth = 472
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnClose = FormClose
OnDestroy = FormDestroy
OnShow = FormShow
PixelsPerInch = 96
TextHeight = 13
object Label1: TLabel
Left = 0
Top = 0
Width = 472
Height = 13
Align = alTop
Caption = 'Label1'
ExplicitLeft = 232
ExplicitTop = 136
ExplicitWidth = 31
end
object Timer1: TTimer
Enabled = False
OnTimer = Timer1Timer
Left = 232
Top = 128
end
object Timer2: TTimer
Enabled = False
Interval = 1
OnTimer = Timer2Timer
Left = 320
Top = 168
end
end
dll code
library dllapp;
uses
System.SysUtils,
Themes,
Windows,
Forms,
dialogs,
Graphics,
Vcl.ExtCtrls,
Unit1 in 'Unit1.pas' {Unit1},
DThreadsend in 'Thread.pas';
var
mHandle: THandle;
DLLHandle: Longint = 0;
function createApp(Width: Integer; Height: Integer; hw: HWnd;
app: TApplication): boolean; stdcall;
begin
mHandle := CreateMutex(nil, True, 'APPNAMETLOAD');
if GetLastError = ERROR_ALREADY_EXISTS then
begin
Halt;
end;
try
form1 := Tform1.CreateParented(hw); // **
form1.Width := Width;
form1.Height := Height;
Result := True
except
on e: exception do
begin
Result := False;
end;
end;
end;
procedure closeApp; stdcall;
begin
ApplicationClosed := True;
try
if mHandle <> 0 then
CloseHandle(mHandle);
except
end;
if Assigned(form1) then
try
FreeAndNil(form1);
except
end;
try
OptimizeRamUsage;
except
end;
end;
procedure showapp; stdcall;
begin
try
form1.Visible := True;
except
end;
form1.Show;
end;
procedure DLLEntryProc(EntryCode: Integer);
begin
case EntryCode of
DLL_PROCESS_DETACH:
begin
StyleServices.Free;
end;
DLL_PROCESS_ATTACH:
begin
end;
DLL_THREAD_ATTACH:
begin
end;
DLL_THREAD_DETACH:
begin
end;
end;
end;
exports
closeApp,
createApp,
showapp;
begin
DllProc := #DLLEntryProc;
end.
Host Application and how i create Dll
loadapp Unit
unit loadapp;
interface
uses windows, forms, System.SysUtils , dialogs;
procedure loadmainapp;
type
TcreaFunc = function (Width: Integer; Height: Integer; hw:HWnd; app: TApplication): boolean; stdcall;
TshowFunc = procedure stdcall;
TCloseAppFunc = procedure stdcall;
var
dllHandle : THandle = 0;
creaFunc : TcreaFunc;
showFunc : TshowFunc;
CloseAppFunc: TCloseAppFunc;
implementation
uses Mainapp;
procedure loadmainapp;
var
S: widestring;
PW: PWideChar;
begin
S := 'dllapp.dll';
pw:=pwidechar(widestring(s));
dllHandle := LoadLibrary(pw);
if dllHandle <> 0 then
begin
#creaFunc := GetProcAddress(dllHandle, 'createApp');
#showFunc := GetProcAddress(dllHandle, 'showapp');
if Assigned (creaFunc) then
begin
creaFunc(mainfrm.panel1.Width, mainfrm.panel1.Height, mainfrm.panel1.Handle, Application);
DisFunc;
end
else
ShowMessage('ERROR');
end
else
begin
ShowMessage('ERROR');
end;
end;
end.
Active Form
unit activeform;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ActiveX, AxCtrls, Frmldr_TLB, StdVcl, Vcl.ExtCtrls, ShlObj, Vcl.StdCtrls, SHDocVw, MSHTML;
type
TActiveFrmldr = class(TActiveForm, IActiveFrmldr)
mpanl: TPanel;
Timer1: TTimer;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
end;
implementation
uses ComObj, ComServ, Mainapp, libacload;
{$R *.DFM}
{ TActiveFrmldr }
procedure TActiveFrmldr.FormDestroy(Sender: TObject);
begin
if dllHandle <> 0 then
begin
#CloseAppFunc := GetProcAddress(dllHandle, 'closeApp');
CloseAppFunc;
FreeLibrary(dllHandle); //release dll
end;
if Assigned(mainfrm) then
try
FreeAndNil(mainfrm);
except
end;
end;
procedure TActiveFrmldr.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled := False;
mainfrm.Parent := mpanl;
mainfrm.Left := 0;
mainfrm.Top := 0;
mainfrm.Width := self.Width;
mainfrm.Height := self.Height;
mainfrm.Align := alClient;
mainfrm.Show;
end;
procedure TActiveFrmldr.FormCreate(Sender: TObject);
begin
Application.CreateForm(Tmainfrm, mainfrm);
Timer1.Enabled := True;
end;
initialization
TActiveFormFactory.Create(
ComServer,
TActiveFormControl,
TActiveFrmldr,
Class_ActiveFrmldr,
0,
'',
OLEMISC_SIMPLEFRAME or OLEMISC_ACTSLIKELABEL,
tmApartment);
finalization
end.
Main app Form that call load library function
unit Mainapp;
interface
uses
Windows, Messages, System.SysUtils, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, System.Classes, libacload,
Vcl.Controls, Vcl.StdCtrls;
type
Tmainfrm = class(TForm)
Panel1: TPanel;
Timer1: TTimer;
Timer2: TTimer;
procedure FormShow(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure Timer2Timer(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
mainfrm: Tmainfrm;
implementation
Uses loadapp;
{$R *.dfm}
procedure Tmainfrm.FormShow(Sender: TObject);
begin
Timer1.Enabled := True;
end;
procedure Tmainfrm.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled := False;
loadmainapp;
end;
procedure Tmainfrm.Timer2Timer(Sender: TObject);
begin
checksynchronize; // i do this to check some thread in activex it self
end;
end.
The error means that CheckSynchronize() is being called in a thread whose ThreadID does not match the RTL's global System.MainThreadID variable.
A DLL does not have a main thread of its own. MainThreadID gets initialized to whatever thread is initializing the DLL. So, if your DLL is creating its GUI in a different thread than the one that is initializing your DLL, CheckSynchronize() (and TThread.Synchronize(), and TThread.Queue()) will not work unless you manually update the MainThreadID variable to the ThreadID that is running your GUI. Do that before creating your worker thread, eg:
if IsLibrary then
MainThreadID := GetCurrentThreadID;
Form1 := TForm1.Create(nil);
Or:
procedure TForm1.FormCreate(Sender: TObject);
begin
if IsLibrary then
MainThreadID := GetCurrentThreadID;
end;
Or:
procedure TForm1.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled := False;
if IsLibrary then
MainThreadID := GetCurrentThreadID;
APPTHREAD := TAPPTHREAD.Create(false, CallbackProc);
Timer2.Enabled := True;
end;
Related
I have a Main Form that creates a Thread.
The Thread creates a Form with a Progress bar.
What I'm trying to do is create the Thread from the Main Form and send a message to the Thread to increase the Progress bar on the Thread Form.
This will allow me to execute code and provide the user with the progress.
So far I have the Main Form:-
unit uMain;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, contnrs,
StdCtrls, uThread, ExtCtrls;
type
TMainForm = class(TForm)
btnCreateForm: TButton;
btnSendMessage: TButton;
procedure btnCreateFormClick(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure btnSendMessageClick(Sender: TObject);
private
{ Private declarations }
MyProgressBarThread: TProgressBarThread;
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
procedure TMainForm.btnCreateFormClick(Sender: TObject);
begin
MyProgressBarThread := TProgressBarThread.Create(Self);
end;
procedure TMainForm.btnSendMessageClick(Sender: TObject);
begin
// Is this correct way to send a message to the Thread?
PostThreadMessage(MyProgressBarThread.Handle, WM_USER, 0, 0);
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
if Assigned(MyProgressBarThread) then
MyProgressBarThread.Terminate;
end;
end.
And the Thread:-
unit uThread;
interface
uses
Forms, StdCtrls, Graphics, ExtCtrls, ClipBrd, Contnrs, JPeg, SysUtils,
ComCtrls, System.Classes{taRightJustify}, Winapi.Messages, Winapi.Windows;
type
TProgressBarThread = class(TThread)
private
{ Private declarations }
FForm: TForm;
FUse_Progress_Position_Label: Boolean;
lbProcessing_Name: TLabel;
lbProcessing_Description: TLabel;
lbProcessing_Position_Number: TLabel;
ProgressBar1: TProgressBar;
procedure OnCloseForm(Sender: TObject; var Action: TCloseAction);
procedure OnDestroyForm(Sender: TObject);
protected
procedure Execute; override;
public
constructor Create(AForm: TForm);
end;
implementation
{ TProgressBarThread }
constructor TProgressBarThread.Create(AForm: TForm);
begin
FForm := TForm.Create(nil);
lbProcessing_Name := TLabel.Create(FForm);
ProgressBar1 := TProgressBar.Create(FForm);
lbProcessing_Description := TLabel.Create(FForm);
lbProcessing_Position_Number := TLabel.Create(FForm);
with FForm do
begin
Caption := 'Please Wait...';
Left := 277;
Top := 296;
BorderIcons := [biSystemMenu];
BorderStyle := bsSingle;
ClientHeight := 80;
ClientWidth := 476;
Color := clBtnFace;
Font.Color := clWindowText;
Font.Height := -11;
Font.Name := 'MS Sans Serif';
Font.Style := [];
FormStyle := fsStayOnTop;
OldCreateOrder := False;
Position := poMainFormCenter;
PixelsPerInch := 96;
OnClose := OnCloseForm;
OnDestroy := OnDestroyForm;
with lbProcessing_Name do
begin
Parent := FForm;
Left := 16;
Top := 24;
Width := 130;
Height := 13;
Caption := 'Processing Request... ';
Font.Color := clWindowText;
Font.Height := -11;
Font.Name := 'MS Sans Serif';
Font.Style := [fsBold];
ParentFont := False;
end;
with lbProcessing_Description do
begin
Parent := FForm;
Left := 160;
Top := 24;
Width := 3;
Height := 13;
Font.Color := clBlue;
Font.Height := -11;
Font.Name := 'MS Sans Serif';
Font.Style := [];
ParentFont := False;
end;
with lbProcessing_Position_Number do
begin
Parent := FForm;
Left := 456;
Top := 24;
Width := 6;
Height := 13;
Alignment := taRightJustify;
Caption := '0';
Visible := False;
Font.Color := clBlue;
Font.Height := -11;
Font.Name := 'MS Sans Serif';
Font.Style := [];
end;
with ProgressBar1 do
begin
Parent := FForm;
Left := 16;
Top := 48;
Width := 449;
Height := 17;
TabOrder := 0;
end;
end;
FForm.Show;
inherited Create(False);
end;
procedure TProgressBarThread.Execute;
var
Msg: TMsg;
begin
FreeOnTerminate := True;
// Is this the correct way to Look for Messages sent to the Thread and to handle them?
while not (Terminated or Application.Terminated) do
begin
if PeekMessage(&Msg, 0, 0, 0, PM_NOREMOVE) then
begin
if Msg.message > 0 then
ProgressBar1.Position := ProgressBar1.Position + 1;
end;
end;
end;
procedure TProgressBarThread.OnCloseForm(Sender: TObject; var Action: TCloseAction);
begin
Terminate;
// WaitFor;
end;
procedure TProgressBarThread.OnDestroyForm(Sender: TObject);
begin
if not Terminated then
begin
Terminate;
WaitFor;
end;
end;
end.
Is this the correct way to go for my situation? If not then any examples?
Is the PostThreadMessage(MyProgressBarThread.Handle, WM_USER, 0, 0); correct?
How do I Listening for messages in the Thread and process them?
tia
Updated based on comments 09/07/2021
Is this code correct and safe:-
MainForm
unit uMain;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, contnrs,
StdCtrls, uThread, ExtCtrls;
type
TMainForm = class(TForm)
btnStart_Process: TButton;
procedure btnStart_ProcessClick(Sender: TObject);
private
{ Private declarations }
Start_ProcessThread: TStart_ProcessThread;
procedure TheCallback(const ProgressBarPosition: Integer);
public
{ Public declarations }
end;
var
MainForm: TMainForm;
hLogWnd: HWND = 0;
implementation
uses
uProgressBar;
{$R *.DFM}
procedure TMainForm.btnStart_ProcessClick(Sender: TObject);
begin
frmProgressBar.ProgressBar1.Max := Con_Max_ProgressBarPosition;
frmProgressBar.ProgressBar1.Position := 0;
frmProgressBar.Show;
Start_ProcessThread := TStart_ProcessThread.Create(TheCallback);
end;
procedure TMainForm.TheCallback(const ProgressBarPosition: Integer);
begin
if ProgressBarPosition <> Con_Finished_Processing then
frmProgressBar.ProgressBar1.Position := ProgressBarPosition
else
frmProgressBar.Close;
end;
end.
ProgressBarForm
unit uProgressBar;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ComCtrls;
type
TfrmProgressBar = class(TForm)
ProgressBar1: TProgressBar;
private
{ Private declarations }
public
{ Public declarations }
end;
var
frmProgressBar: TfrmProgressBar;
implementation
{$R *.dfm}
end.
Thread
unit uThread;
interface
uses
Forms, StdCtrls, Graphics, ExtCtrls, ClipBrd, Contnrs, JPeg, SysUtils,
ComCtrls, System.Classes{taRightJustify}, Winapi.Messages, Winapi.Windows;
const
Con_Finished_Processing = -1;
Con_Max_ProgressBarPosition = 1024 * 65536;
type
TMyCallback = procedure(const ProgressBarPosition: Integer) of object;
TStart_ProcessThread = class(TThread)
private
FCallback : TMyCallback;
procedure Execute; override;
procedure SendLog(I: Integer);
public
constructor Create(aCallback : TMyCallback);
end;
implementation
{ TStart_ProcessThread }
constructor TStart_ProcessThread.Create(aCallback: TMyCallback);
begin
inherited Create(false);
FCallback := aCallback;
end;
procedure TStart_ProcessThread.SendLog(I: Integer);
begin
if not Assigned(FCallback) then
Exit;
Self.Queue( // Executed later in the main thread
procedure
begin
FCallback(I{ThePosition});
end
);
end;
procedure TStart_ProcessThread.Execute;
var
I: Integer;
begin
// Do the Work Load here:-
for I := 0 to Con_Max_ProgressBarPosition do
begin
if ((I mod 65536) = 0) then
begin
// Send back the progress of the work here:-
SendLog(I);
Sleep(10);
end;
end;
// Finished
SendLog(Con_Finished_Processing);
end;
end.
If your going for additional Components: i would suggest looking at Omni Thread Library.
http://www.omnithreadlibrary.com/book/chap10.html#leanpub-auto-sending-data-from-a-worker-to-a-form
The 7.13.2 Example in the current Version (Sending data from a worker to a form)
Its a great Library and the free book from the link above is a good source for many multithreaded Scenarios.
I use almost every Time the 3.2 Blocking collection (in the text there is a link to the demo source) Its not specially what your looking for, but a combination of both should be powerfull to create multihreaded chains of workloads.
kbmMW contains a feature called SmartEvent which I would recommend checking out. It works great in such scenarios, where you want different parts of your code (threaded or not) to communicate with each other and transfer data.
It works as simple as this:
TForm1 = class(...)
...
private
procedure FormCreate(Sender: TObject);
public
[kbmMW_Event('UPDATESTATUS',[mweoSync])]
procedure UpdateStatus(const APct:integer);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Event.Subscribe(self);
end;
procedure TForm1.UpdateStatus(const APct:integer);
begin
Label1.Caption:='Pct='+inttostr(APct);
end;
and then in your thread do:
procedure TYourThread.Execute;
begin
...
Event.Notify('UPDATESTATUS',pct);
...
end;
All thread synchronization etc. will be handled automatically for you.
You can even make calls, expecting data back, and there can be any number of subscribers for your notifications.
kbmMW is a toolbox that fully supports Delphi and all platforms.
You can read more about SmartEvent here: https://components4developers.blog/2019/11/11/smartevent-with-kbmmw-1/
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).
My initial question was misleading, I'll try to improve it:
I was able to write a small delphi programme, which is able to do a threaded download by using indy's idHTTP. It consists of 2 files:
the form:
interface
type
TForm1 = class(TForm)
Button1: TButton;
Label1: TLabel;
Button2: TButton;
IdSSLIOHandlerSocketOpenSSL1: TIdSSLIOHandlerSocketOpenSSL;
IdAntiFreeze1: TIdAntiFreeze;
IdHTTP: TIdHTTP;
ProgressBar1: TProgressBar;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private-Deklarationen }
public
{ Public-Deklarationen }
procedure UpdateLabel(BytesDone: Integer);
procedure UpdateProgressBar(AWorkCount: Int64);
procedure InitProgressBar(AWorkCountMax: Int64);
procedure ResetProgressBar;
end;
var
Form1: TForm1;
uStopDownloading: Boolean;
implementation
{$R *.dfm}
uses Unit2;
procedure TForm1.Button1Click(Sender: TObject);
var
HTTPThread: TIdHTTPThread;
begin
HTTPThread := TIdHTTPThread.Create(True);
HTTPThread.Url := 'https://www.bot-factory.de/tmp/lorem7.txt';
HTTPThread.EncodedStr := '';
HTTPThread.Filename := 'C:\test.txt';
HTTPThread.FreeOnTerminate := True;
HTTPThread.Resume;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
idhttp.Disconnect;
end;
procedure TForm1.UpdateLabel(BytesDone: Integer);
begin
Label1.caption := format('%.0n',[extended(BytesDone+0.0)]) +' bytes loaded.';
end;
procedure TForm1.UpdateProgressBar(AWorkCount: Int64);
begin
ProgressBar1.Position := AWorkCount;
end;
procedure TForm1.InitProgressBar(AWorkCountMax: Int64);
begin
Screen.Cursor := crHourGlass;
ProgressBar1.Max := AWorkCountMax;
ProgressBar1.Position := 0;
end;
procedure TForm1.ResetProgressBar;
begin
Screen.Cursor := crDefault;
showmessage('Job is done');
end;
END.
And the Thread-Unit:
interface
uses
Classes, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, IdHTTP, sysutils;
type
TIdHTTPThread = class(TThread)
private
FURL: AnsiString;
FencodedStr: string;
FFilename: AnsiString;
FBytesDone,FProgress,FWorkCountMax: Int64;
IdHTTP: TIdHTTP;
procedure OnWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64);
procedure OnWorkBegin(ASender: TObject; AWorkMode: TWorkMode; AWorkCountMax: Int64);
procedure OnWorkEnd(ASender: TObject; AWorkMode: TWorkMode);
procedure Updatelabel;
procedure UpdateProgressBar;
procedure InitProgressBar;
procedure ResetProgressBar;
procedure Disconnect;
public
Constructor Create(CreateSuspended: Boolean);
Destructor Destroy; override;
property Url: AnsiString read FURL write FUrl;
property encodedstr: String read FencodedStr write FencodedStr;
property Filename: AnsiString read FFilename write FFilename;
protected
procedure Execute; override;
end;
implementation
uses
Unit1; // Formular Unit
constructor TIdHTTPThread.Create(CreateSuspended: Boolean);
begin
inherited Create(Suspended);
IdHTTP := TIdHTTP.Create;
IdHTTP.OnWork := OnWork;
IdHTTP.OnWorkBegin := OnWorkBegin;
IdHTTP.OnWorkEnd := OnWorkEnd;
//IdHTTP.Disconnect := Disconnect;
end;
destructor TIdHTTPThread.Destroy;
begin
IdHTTP.Free;
inherited;
end;
procedure TIdHTTPThread.Execute;
var
DestStream: TFileStream;
begin
DestStream := TFileStream.Create(Filename, fmCreate);
try
IdHTTP.Get(Url, DestStream);
finally
DestStream.Free;
end;
end;
procedure TIdHTTPThread.OnWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64);
begin
FBytesDone := AWorkCount;
FProgress := AWorkCount;
Synchronize(Updatelabel);
Synchronize(UpdateProgressBar);
end;
procedure TIdHTTPThread.OnWorkBegin(ASender: TObject; AWorkMode: TWorkMode; AWorkCountMax: Int64);
begin
FWorkCountMax := AWorkCountMax;
Synchronize(InitProgressBar);
end;
procedure TIdHTTPThread.Disconnect;
begin
idhttp.Disconnect;
end;
procedure TIdHTTPThread.OnWorkEnd(ASender: TObject; AWorkMode: TWorkMode);
begin
Synchronize(ResetProgressBar);
end;
procedure TIdHTTPThread.Updatelabel;
begin
Form1.UpdateLabel(FBytesDone);
end;
procedure TIdHTTPThread.UpdateProgressBar;
begin
Form1.UpdateProgressBar(FProgress);
end;
procedure TIdHTTPThread.InitProgressBar;
begin
Form1.initProgressBar(FWorkCountMax);
end;
procedure TIdHTTPThread.resetProgressBar;
begin
Form1.resetProgressBar;
end;
END.
I have in fact 2 questions:
How can I interrupt the download of a (large) file ? I know that
idhttp.disconnect should do the trick, but I do not know how to use it properly in my thread.
And furthermore: how can I use POST instead of GET?
I need to run this POST in a thread:
{++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//submit_post
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
Procedure submit_post(url_string,EncodedStr,filename:string);
var
aStream: TMemoryStream;
Params: TStringStream;
begin
astream := TMemoryStream.create;
Params := TStringStream.create('');
Form1.IdHTTP.Request.Clear;
Form1.IdHTTP.HandleRedirects := TRUE;
try
with Form1.IdHTTP do
begin
Params.WriteString(EncodedStr);
Request.ContentType := 'application/x-www-form-urlencoded';
Request.Charset := 'utf-8';
try
Response.KeepAlive := False;
Post(url_string, params, astream);
except
on E: Exception do
begin
exit;
end;
end;
end;
astream.WriteBuffer(#0' ', 1);
astream.Position := 0;
astream.SaveToFile(filename);
finally
astream.Free;
Params.Free;
end;
end;
When program starts, automatically downloads given EXE file, but if I want to abort the current process and restart to download again or/and if EXE is downloaded successfully one time and would like to download again, program stops with error message: "raised exception class EIdHTTPProtocolException"
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs,idhttp, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient,
ComCtrls, StdCtrls;
type
TForm1 = class(TForm)
ProgressBar1: TProgressBar;
IdHTTP1: TIdHTTP;
Button1: TButton;
Button2: TButton;
procedure FormCreate(Sender: TObject);
procedure IdHTTP1WorkBegin(ASender: TObject; AWorkMode: TWorkMode;
AWorkCountMax: Integer);
procedure IdHTTP1Work(ASender: TObject; AWorkMode: TWorkMode;
AWorkCount: Integer);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
procedure DownloadFile;
end;
type
xy = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
procedure friss;
end;
var
Form1: TForm1;
szal:xy;
Stream: TMemoryStream;
implementation
{$R *.dfm}
procedure xy.friss;
begin
ShowMessage('kész');
szal.terminate;
end;
procedure TForm1.Button1Click(Sender: TObject); //abort
begin
szal.Suspend;
szal.Terminate;
end;
procedure TForm1.Button2Click(Sender: TObject); //restart
begin
szal:=xy.Create(true);
szal.Resume;
end;
procedure tform1.DownloadFile;
var
Url, FileName: String;
begin
idhttp1:=idhttp1.Create(self);
Url := 'http://livecd.com/downloads/ActiveDataStudioSetup.exe';
Filename := 'c:\setup.zip';
Stream := TMemoryStream.Create;
try
IdHTTP1.Get(Url, Stream);
Stream.SaveToFile(FileName);
finally
Stream.Free;
IdHTTP1.free;
end;
end;
procedure xy.execute;
begin
form1.DownloadFile;
Synchronize(friss);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
szal:=xy.Create(true);
szal.Resume;
end;
procedure TForm1.IdHTTP1Work(ASender: TObject; AWorkMode: TWorkMode;
AWorkCount: Integer);
begin
form1.ProgressBar1.Position:=AWorkCount;
end;
procedure TForm1.IdHTTP1WorkBegin(ASender: TObject; AWorkMode: TWorkMode;
AWorkCountMax: Integer);
begin
form1.ProgressBar1.Max:=AWorkCountMax;
form1.ProgressBar1.Position:=0;
end;
end.
Source code: http://pastebin.com/9DvSyTD7
Project: http://osztott.com/ubXN/cucc.zip
EIdHTTPProtocolException means the HTTP server sent back an error, such as if the requested resource is not found or cannot be accessed. That has nothing to do with your threading logic.
However, there are a lot of problems with your code in general - misuse of TThread and dynamic components, not syncing the worker thread with the main UI thread, etc.
Try something more like this instead:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ComCtrls, StdCtrls;
type
TForm1 = class(TForm)
ProgressBar1: TProgressBar;
Button1: TButton;
Button2: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
procedure StartDownload;
procedure StopDownload;
procedure DownloadFinished(Sender: TObject);
public
end;
var
Form1: TForm1;
implementation
uses
IdHTTP, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, IdSync;
{$R *.dfm}
type
TDownloadThread = class(TThread)
private
{ Private declarations }
procedure HTTPWorkBegin(ASender: TObject; AWorkMode: TWorkMode; AWorkCountMax: Integer);
procedure HTTPWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Integer);
protected
procedure Execute; override;
public
property ReturnValue;
property Terminated;
end;
TDownloadStatusNotify = class(TIdNotify)
protected
Value: Integer;
DownloadBegin: Boolean;
procedure DoNotify; override;
public
constructor Create(AValue: Integer: ADownloadBegin: Boolean); reintroduce;
end;
TFreeDownloadThreadNotify = class(TIdNotify)
protected
Thread: TDownloadThread;
procedure DoNotify; override;
public
constructor Create(AThread: TDownloadThread); reintroduce;
end;
procedure TDownloadThread.Execute;
var
Url, Filename: string;
HTTP: TIdHTTP;
Stream: TMemoryStream;
begin
Url := 'http://livecd.com/downloads/ActiveDataStudioSetup.exe';
Filename := 'c:\setup.zip';
HTTP := TIdHTTP.Create(nil);
try
HTTP.OnWorkBegin := HTTPWorkBegin;
HTTP.OnWork := HTTPWork;
Stream := TMemoryStream.Create;
try
HTTP.Get(Url, Stream);
Stream.SaveToFile(Filename);
finally
Stream.Free;
end;
finally
HTTP.Free;
end;
ReturnValue := 1;
end;
procedure TDownloadThread.HTTPWorkBegin(ASender: TObject; AWorkMode: TWorkMode; AWorkCountMax: Integer);
begin
if Terminated then SysUtils.Abort;
if AWorkMode = wmRead then
TDownloadStatusNotify.Create(AWorkCountMax, True).Notify;
end;
procedure TDownloadThread.HTTPWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Integer);
begin
if Terminated then SysUtils.Abort;
if AWorkMode = wmRead then
TDownloadStatusNotify.Create(AWorkCount, False).Notify;
end;
constructor TDownloadStatusNotify.Create(AValue: Integer; ADownloadBegin: Boolean);
begin
inherited Create;
Value := AValue;
DownloadBegin := ADownloadBegin;
end;
procedure TDownloadStatusNotify.DoNotify;
begin
if DownloadBegin then
begin
Form1.ProgressBar1.Position := 0;
Form1.ProgressBar1.Max := Value;
end else
begin
if Form1.ProgressBar1.Max > 0 then
begin
Form1.ProgressBar1.Position := Value;
end else
begin
// the download size is unknown (most likely chunked) so
// display the current Value somewhere else...
end;
end;
end;
constructor TFreeDownloadThreadNotify.Create(AThread: TDownloadThread);
begin
inherited Create;
MainThreadUsesNotify := True;
Thread := AThread;
end;
procedure TFreeDownloadThreadNotify.DoNotify;
begin
Thread.Free;
end;
var
szal: TDownloadThread = nil;
procedure TForm1.FormCreate(Sender: TObject);
begin
StartDownload;
end;
procedure TForm1.Button1Click(Sender: TObject); //abort
begin
StopDownload;
end;
procedure TForm1.Button2Click(Sender: TObject); //restart
begin
StopDownload;
StartDownload;
end;
procedure TForm1.StartDownload;
begin
szal := TDownloadThread.Create(True);
sza1.OnTerminate := DownloadFinished;
szal.Resume;
end;
procedure TForm1.StopDownload;
begin
if sza1 <> nil then
begin
szal.Terminate;
sza1.WaitFor;
FreeAndNil(sza1);
end;
end;
procedure TForm1.DownloadFinished(Sender: TObject);
begin
if sza1.ReturnValue = 1 then
ShowMessage('kész')
else if sza1.Terminated then
ShowMessage('félbeszakadt')
else
ShowMessage('hiba');
if not sza1.Terminated then
begin
TFreeDownloadThreadNotify.Create(sza1).Notify;
sza1 := nil;
end;
end;
end.
I have 4 threads created at runtime. Each thread enters critical section, changes global variable, exits critical section and shows message dialog with the result. OnThreadTerminate I also have a message dialog. It seems to be random, but still, I sometimes get 3 messages with the result and one saying that thread is terminated. How is it even possible? Win7 x64.
There is my full 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, Vcl.Buttons, Vcl.ComCtrls,
IdThreadComponent, idHTTP, SyncObjs;
const
THREAD_NAME = 'MyidThreadComponent';
type
TForm1 = class(TForm)
StatusBar1: TStatusBar;
BitBtn1: TBitBtn;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure BitBtn1Click(Sender: TObject);
private
{ Private declarations }
FCriticalSection: TCriticalSection;
FGlobalVariable: integer;
procedure CreateThreads(const ACount: integer; const AStart: boolean);
function GetWebsiteContent(const AURL: string): string;
procedure MyIdThreadComponentOnRunHandler(Sender: TIdThreadComponent);
procedure MyIdThreadComponentOnTerminateHandler(Sender: TIdThreadComponent);
public
{ Public declarations }
property GlobalVariable: integer read FGlobalVariable write FGlobalVariable;
property CriticalSection: TCriticalSection read FCriticalSection;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
FCriticalSection := TCriticalSection.Create;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FreeAndNil(FCriticalSection);
end;
function TForm1.GetWebsiteContent(const AURL: string): string;
var
_MyidHTTP: TidHTTP;
begin
_MyidHTTP := TidHTTP.Create(self);
try
Result := _MyidHTTP.Get(AURL);
finally
FreeAndNil(_MyidHTTP);
end;
end;
procedure TForm1.MyIdThreadComponentOnRunHandler(Sender: TIdThreadComponent);
var
_LocalVariable: integer;
begin
CriticalSection.Acquire;
try
// Safe way to deal with global variables. Only one thread will enter
// CriticalSection at time.
_LocalVariable := GlobalVariable;
_LocalVariable := _LocalVariable * 2;
GlobalVariable := _LocalVariable;
finally
CriticalSection.Release;
end;
ShowMessage(Sender.Name + ' started: ' + IntToStr(_LocalVariable));
Sender.Terminate;
end;
procedure TForm1.MyIdThreadComponentOnTerminateHandler
(Sender: TIdThreadComponent);
begin
ShowMessage(Sender.Name + ' terminated.');
end;
procedure TForm1.BitBtn1Click(Sender: TObject);
begin
GlobalVariable := 1;
CreateThreads(4 { System.CPUCount + 1 } , true);
end;
procedure TForm1.CreateThreads(const ACount: integer; const AStart: boolean);
var
_MyIdThreadComponent: TIdThreadComponent;
i: integer;
begin
if ACount > 0 then
for i := 1 to ACount do
begin
_MyIdThreadComponent := FindComponent(THREAD_NAME + IntToStr(i))
as TIdThreadComponent;
if not Assigned(_MyIdThreadComponent) then
begin
_MyIdThreadComponent := TIdThreadComponent.Create(self);
_MyIdThreadComponent.Name := THREAD_NAME + IntToStr(i);
_MyIdThreadComponent.Tag := i;
_MyIdThreadComponent.OnRun := MyIdThreadComponentOnRunHandler;
_MyIdThreadComponent.OnTerminate :=
MyIdThreadComponentOnTerminateHandler;
{$IFDEF MSWINDOWS}
_MyIdThreadComponent.Priority := tpNormal;
{$ENDIF}
{$IFDEF MACOS}
_MyIdThreadComponent.Priority := 1;
{$ENDIF}
end;
if AStart = true then
if Assigned(_MyIdThreadComponent) then
_MyIdThreadComponent.Start;
end;
end;
end.
Showmessage is not the best way to show the output as its not thread safe. Instead, if you use a memo or other control and wrap it in a synchronize call it will be easier to see the results. I modified your routine to output to a memo, and included the ThreadId before and inside the synchronize call so you can better understand what is happening.
Keep in mind that your threads will not always output in the order you may think they will, it is entirely possible that thread 4 will output before thread 1, even though thread 1 was started first and 4 last.
procedure TForm13.MyIdThreadComponentOnRunHandler(Sender: TIdThreadComponent);
var
_LocalVariable: integer;
_LocalThreadId : Cardinal;
begin
fCriticalSection.Acquire;
try
// Safe way to deal with global variables. Only one thread will enter
// CriticalSection at time.
_LocalVariable := GlobalVariable;
_LocalVariable := _LocalVariable * 2;
GlobalVariable := _LocalVariable;
finally
fCriticalSection.Release;
end;
_LocalThreadId := TThread.CurrentThread.ThreadID;
TThread.Synchronize(TThread.CurrentThread,procedure begin
memo1.Lines.Add(Format('%s Started (%d/%d): %d',[Sender.Name,_LocalThreadId,TThread.CurrentThread.ThreadID,_LocalVariable]));
end);
Sender.Terminate;
end;
procedure TForm13.MyIdThreadComponentOnTerminateHandler
(Sender: TIdThreadComponent);
begin
// note sync call is not needed as this is executed in the context of the main thread.
memo1.Lines.Add(Format('%s terminated. (%d)',[Sender.Name,TThread.CurrentThread.ThreadID]));
end;