Working with Delphi TThread - multithreading

This is the first time to try working with Threads, I'm trying to copy a directory using a Thread, so here is what I did (After I read this post):
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.IOUtils, System.Types;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
TMyThread= class(TThread)
private
Fsource, FDest: String;
protected
public
constructor Create(Const Source, Dest: string);
destructor Destroy; override;
procedure Execute(); override;
published
end;
var
Form1: TForm1;
MT: TMyThread;
implementation
{$R *.dfm}
{ TMyThread }
constructor TMyThread.Create(const Source, Dest: string);
begin
Fsource:= Source;
FDest:= Dest;
end;
destructor TMyThread.Destroy;
begin
inherited;
end;
procedure TMyThread.Execute;
var Dir: TDirectory;
begin
inherited;
try
Dir.Copy(Fsource, FDest);
except on E: Exception do
ShowMessage(E.Message);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
MT := TMyThread.Create('SourceFolder', 'DestinationFolder');
try
MT.Execute;
finally
MT.Free;
end;
end;
end.
When I click on the Button1 I get this error message:
Cannot call Start on a running or suspended thread
What's wrong here? I don't know much about threads,I even try:
MT := TMyThread.Create('SourceFolder', 'DestinationFolder');

Thanks for all guys helps with helpful comments:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.IOUtils, System.Types;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
TMyThread= class(TThread)
private
Fsource, FDest: String;
protected
public
constructor Create(Const Source, Dest: string);
destructor Destroy; override;
procedure Execute(); override;
published
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TMyThread }
constructor TMyThread.Create(const Source, Dest: string);
begin
inherited Create;
Fsource:= Source;
FDest:= Dest;
Self.FreeOnTerminate := True;
end;
destructor TMyThread.Destroy;
begin
inherited;
end;
procedure TMyThread.Execute;
begin
try
TDirectory.Copy(Fsource, FDest);
except on E: Exception do
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var MT: TMyThread;
begin
MT := TMyThread.Create('Source', 'Destination');
end;
end.

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.

How to cancel a synchronous IO operation?

I wrote this code to test how async IO cancellation works... But the read operation it's not canceled... When I call CancelSynchronousIo, the execution is blocked until thread finishes and then I get Element not found error. Do you have any idea what I did wrong ?
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
TMyThread = class(TThread)
protected
procedure Execute; override;
end;
TForm1 = class(TForm)
BStartRead: TButton;
Memo1: TMemo;
BCancel: TButton;
procedure BStartReadClick(Sender: TObject);
procedure BCancelClick(Sender: TObject);
private
T1: TMyThread;
procedure LogMsg(var Msg: TMessage); message WM_USER;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TMyThread.Execute;
var Buff: TBytes;
hFile: THandle;
StrMsg, FileName: String;
N: Cardinal;
begin
SetLength(Buff, 200000000);
FileName:= '...some file...';
hFile:= CreateFile(PChar('\\?\'+FileName), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if hFile <> INVALID_HANDLE_VALUE then begin
StrMsg:= 'Start reading...'; SendMessage(Form1.Handle, WM_USER, WPARAM(#StrMsg), 0);
if ReadFile(hFile, Buff[0], 200000000, N, nil)
then StrMsg:= 'Successfuly terminated.'
else StrMsg:= 'Error: canceled !';
SendMessage(Form1.Handle, WM_USER, WPARAM(#StrMsg), 0);
CloseHandle(hFile);
end
else begin
StrMsg:= 'Error: failed to open file !';
SendMessage(Form1.Handle, WM_USER, WPARAM(#StrMsg), 0);
end;
end;
//---- VCL -----
procedure TForm1.BStartReadClick(Sender: TObject);
begin
T1:= TMyThread.Create;
end;
procedure TForm1.BCancelClick(Sender: TObject);
begin
if CancelSynchronousIo(T1.Handle) then Memo1.Lines.Add('Cancel result: success')
else Memo1.Lines.Add('Cancel result: '+SysErrorMessage(GetLastError));
end;
procedure TForm1.LogMsg(var Msg: TMessage);
begin
case Msg.Msg of
WM_USER: Memo1.Lines.Add(PString(Msg.WParam)^.Substring(0));
end;
end;
end.

Anonymous Thread not letting application close

I have an application that the user can run either interactively or from command line. In the second mode, the program must exit upon completion.
Here is the basic minimal code. It seems that application.terminated is never set;
How do I get this program to close, w/o0 user interaction.
unit Unit4;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls ;
type
TForm4 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
procedure DoSomeStuff;
{ Private declarations }
public
{ Public declarations }
end;
var
Form4: TForm4;
implementation
{$R *.dfm}
procedure TForm4.Button1Click(Sender: TObject);
var
anonThread : TThread;
begin
anonThread := TThread.CreateAnonymousThread(procedure
begin
while not application.terminated do
begin
doSomeStuff;
end;
end);
anonThread.Start;
end;
procedure TForm4.DoSomeStuff;
var
i : integer;
begin
for i := 0 to 10 do
begin
beep;
sleep(100);
end;
application.Terminate;
end;
end.
The Application.Terminated property doesn't work without the message loop in Application.Run() (or at least a manual loop that calls Application.ProcessMessages()) in the main UI thread. This is because Application.Terminate() simply posts a WM_QUIT message to the calling thread's message queue. Application.Terminated is not set until that message is processed. A console app typically does not call Application.Run(), so Application.Terminated does not work on a console app.
You should redesign your code to remove the dependency on Application and your TForm in console mode, eg:
program MyApp;
uses
MyWorkUnit, Unit4;
begin
if IsRunInCommandLineMode then
begin
DoSomeStuff;
end else
begin
Application.Initialize;
Application.CreateForm(TForm4, Form4)
Application.Run;
end;
end.
unit MyWorkUnit;
interface
procedure DoSomeStuff;
implementation
procedure DoSomeStuff;
var
i : Integer;
begin
for i := 0 to 10 do
begin
Beep;
Sleep(100);
end;
end;
end.
unit Unit4;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm4 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form4: TForm4;
implementation
{$R *.dfm}
uses
MyWorkUnit;
procedure TForm4.Button1Click(Sender: TObject);
begin
TThread.CreateAnonymousThread(
procedure
begin
while not Application.Terminated do
DoSomeStuff;
end
).Start;
end;
end.

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,'');

Delphi exception class Segmentation (11) caused by thread?

When ButtonConnectClick is activated it gives the Segmentation (11) exception.
I think it's caused by pocForm1.AddLine('new line by thread'); in the thread, but I'm not sure how to solve it.
The idea is to add lines to the memo field while the thread is active.
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, FMX.Layouts,
FMX.Memo, FMX.StdCtrls, IdGlobal, IdIntercept;
type
TpocForm1 = class(TForm)
ButtonConnect: TButton;
ButtonDisconnect: TButton;
Memo1: TMemo;
procedure ButtonConnectClick(Sender: TObject);
procedure ButtonDisconnectClick(Sender: TObject);
procedure AddLine(text : String);
private
public
{ Public declarations }
end;
TpocTCPClientThread = class(TThread)
//TCPClient: TIdTCPClient;
protected
procedure Execute; override;
end;
var
pocForm1: TpocForm1;
implementation
{$R *.fmx}
var
thread: TpocTCPClientThread;
procedure TpocForm1.ButtonConnectClick(Sender: TObject);
begin
Memo1.Lines.Add('Client connected with server');
thread:= TpocTCPClientThread.Create(False);
end;
procedure TpocForm1.ButtonDisconnectClick(Sender: TObject);
begin
thread.Terminate;
thread.WaitFor;
FreeAndNil(thread);
Memo1.Lines.Add('Client disconnected from server');
end;
procedure TpocForm1.AddLine(text : String);
begin
Memo1.Lines.Add(text);
end;
procedure TpocTCPClientThread.Execute();
begin
while not Terminated do
begin
pocForm1.AddLine('new line by thread');
end;
end;
end.
You cannot execute GUI code from outside the main thread. You need to ensure that all your GUI code executes on the main thread. For instance by calling TThread.Queue or TThread.Synchronize.

Resources