I have a task I want to run in the background and not interrupt the GUI thread to check for new program versions.
When the application starts, it immediately queues a thread to wait 15 seconds then execute the remainder of the code. If the check is manually triggered before the 15 seconds are up, the existing automatic thread should be terminated. If the application is closed at any point, any remaining threads should be terminated as soon as possible.
I use 2 minutes in the example below for easier debugging.
My issue right now is that no combination that I've tried using WaitFor, Terminate, FreeOnTerminate, and OnTerminated will get the desired result. Either Destroy isn't called and I get memory leaks, the application hangs when terminating a thread, or I get Cannot terminate externally created thread exceptions.
Thread code
unit unCheckThread;
interface
uses SysUtils, Classes, SyncObjs, Dialogs;
type
TCheckThread = class(TThread)
private
FDelayEvent: TEvent;
FDelay: Integer;
public
constructor Create(const ADelay: Integer);
destructor Destroy; override;
procedure Execute; override;
procedure TerminatedSet; override;
end;
implementation
{ TCheckThread }
constructor TCheckThread.Create(const ADelay: Integer);
begin
inherited Create(True);
FreeOnTerminate := False;
FDelay := ADelay;
FDelayEvent := TEvent.Create(nil, True, False, '');
end;
destructor TCheckThread.Destroy;
begin
FDelayEvent.Free;
inherited;
end;
procedure TCheckThread.Execute;
begin
FDelayEvent.WaitFor(MSecsPerSec * FDelay);
{ if another thread has checked while waiting for the delay, cancel this check }
if Terminated then Exit;
{ some long running code }
Sleep(10000);
if Terminated then Exit;
Synchronize(
procedure()
begin
MessageDlg('Thread completed', mtConfirmation, [mbOK], 0);
end);
end;
procedure TCheckThread.TerminatedSet;
begin
FDelayEvent.SetEvent;
end;
end.
UI
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, unCheckThread, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
AThread: TCheckThread;
procedure onterminate(Sender: TObject);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
if Assigned(AThread) then begin
AThread.Terminate;
// AThread.WaitFor; // ?
end;
AThread := TCheckThread.Create(0); // start immediately
AThread.OnTerminate := onterminate;
AThread.Start;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
Close;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if Assigned(AThread) then begin
AThread.Terminate;
// AThread.WaitFor; // ??
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
AThread := TCheckThread.Create(SecsPerMin * 2); // wait for 2 mintues before starting
end;
procedure TForm1.onterminate(Sender: TObject);
begin
FreeAndNil(AThread);
end;
end.
You can't interrupt a Sleep(), so that is not a good choice for debugging, especially for long intervals. You should use FDelayEvent.WaitFor() instead. That way, the thread can "wake up" quickly when it is being terminated.
Also, instead of calling Synchronize() at the end of Execute(), you should use the OnTerminate event instead for any operations you need when the thread is finished. OnTerminate is already synchronized with the main UI thread.
For instance, you can use OnTerminate to set your AThread variable to nil when that thread is terminated, so that your Assigned(AThread) check doesn't fail. You are already trying to do this, however you can't safely Free a thread from inside its OnTerminate event handler, so you might consider using FreeOnTerminate=True instead, or at least delay the Free until after the handler exits.
For that matter, I would not suggest creating a delayed thread at app startup to begin with. Use a TTimer instead. When its OnTimer event is fired, THEN create a non-delayed thread. If the user triggers a manual check, simply disable the timer. This way, you don't waste resources creating a thread that just sits idle, and you don't have to worry about syncing multiple threads to each other.
With that said, try something more like this:
unit unCheckThread;
interface
uses
Classes;
type
TCheckThread = class(TThread)
protected
procedure Execute; override;
public
constructor Create(AOnTerminate: TNotifyEvent);
end;
implementation
uses
SysUtils;
{ TCheckThread }
constructor TCheckThread.Create(AOnTerminate: TNotifyEvent);
begin
inherited Create(False);
FreeOnTerminate := False;
OnTerminate := AOnTerminate;
end;
procedure TCheckThread.Execute;
var
I: Integer;
begin
{ some long running code }
for I := 1 to 20 do
begin
if Terminated then Exit;
Sleep(500);
end;
end;
end.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, unCheckThread;
const
WM_FREE_THREAD = WM_APP + 1;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Timer1: TTimer;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
{ Private declarations }
AThread: TCheckThread;
procedure ThreadFinished(Sender: TObject);
procedure StartThread;
procedure StopThread;
procedure WMFreeThread(var Message: TMessage); message WM_FREE_THREAD;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
StartThread;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
Close;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Timer1.Interval := 120000; // wait for 2 minutes before starting
Timer1.Enabled = True;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
StopThread;
end;
procedure TForm1.StartThread;
begin
Timer1.Enabled := False;
if not Assigned(AThread) then
begin
AThread := TCheckThread.Create(ThreadFinished);
Button1.Enabled := False;
end;
end;
procedure TForm1.StopThread;
begin
if Assigned(AThread) then
begin
AThread.OnTerminate := nil;
AThread.Terminate;
AThread.WaitFor;
FreeAndNil(AThread);
end;
end;
procedure TForm1.ThreadFinished(Sender: TObject);
begin
AThread := nil;
// in 10.2 Tokyo and later, you can use TThread.ForceQueue() instead...
// TThread.ForceQueue(nil, Sender.Free);
PostMessage(Handle, WM_FREE_THREAD, 0, LPARAM(Sender));
MessageDlg('Thread completed', mtConfirmation, [mbOK], 0);
Button1.Enabled := True;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled := False;
StartThread;
end;
procedure TForm1.WMFreeThread(var Message: TMessage);
begin
TObject(Message.LParam).Free;
end;
end.
If:
for whatever reason, you want to keep the structure of your code ...
when you said "If the application is closed at any point, any remaining threads should be terminated as soon as possible." -> it means that we do not really care about the result of any process being run/worked on by the thread ...
Then:
this won't be a "clean", academic, software engineering kind of way
this is just an alternative to simply get things done (targeting 2 objectives: "killing"/terminating a thread and avoiding memory leaks)
Keep your line of code below.
FreeOnTerminate := False;
"Kill"/terminate the thread (regardless of its state).
TerminateThread(AThread.Handle, 0);
TForm1 has/owns AThread, and because your code may re-create AThread (in FormCreate then in Button1Click), free the thread every time right after it has been terminated (currently, you do this in Button1Click and FormClose). Just in case you do if Assigned(AThread) anywhere else, use FreeAndNil (it frees AThread and sets it to nil, this way Assigned(AThread) can correctly return False when the thread has been terminated and freed using your code structure).
FreeAndNil(AThread);
The TerminateThread method (above) needs Winapi.Windows (which you have had in the uses list of your Unit1). It replaces AThread.Terminate.
It would look like below:
if Assigned(AThread) then begin
TerminateThread(AThread.Handle, 0);
FreeAndNil(AThread);
end;
Related
Situation: I have a program that initiates a thread to run continuously in the background. When I terminate this thread, I intermittently get a Runtime Error 204 (Invalid Pointer) and memory leaks.
Code to Create the Thread (it is created in an object TJEList that has a method OnLJ2DOSyncThreadNotification to be called on notification that performs some actions using some of the TStringList objects in the thread):
FLJ2DOSyncThread:=TLJ2DOSyncThread.Create (True);
FLJ2DOSyncThread.NotifyEvent:=OnLJ2DOSyncThreadNotification;
FLJ2DOSyncThread.Start;
The Thread's Execute code:
FreeOnTerminate:=True;
//I create 5 StringList objects here - they are declared as private variables in the Thread
try
while Not Terminated do
begin
//Perform operation (which internally also checks for Terminated)
if Terminated then
Break;
//Perform different operation (which internally also checks for Terminated)
if Terminated then
Break;
//Perform different operation (which internally also checks for Terminated)
if Terminated then
Break;
//etc...
Sleep (1500);
end;
finally
//I FreeAndNil(...) all the TStringlist objects
end;
For good measure (although likely redundant), I also have a destructor in the thread that does:
if Assigned (TStringList object) then FreeAndNil (TStringList object);
for all of the TStringList objects that were created (and then calls inherited;).
My code to stop the thread is:
if Assigned(FLJ2DOSyncThread) then
if FLJ2DOSyncThread.Started then
FLJ2DOSyncThread.Terminate;
I call the code to stop the thread from the destructor of the TJEList object (which gets called on the closure of the main form of the application).
The Issue:
Sometimes, the program terminates cleanly (no memory leaks, or error messages). Other times, I get the following error message and memory leak (for what it's worth, the memory leak message appears before the Runtime error message):
My Question: How do I ensure that the thread always reliably terminates (and therefore gets freed)? Any help and/or guidance would be most appreciated!
UPDATE 20170310 5.08pm HKT: Including the code for MCVE as requested
Program Code:
program ThreadIssueMCVE;
uses
System.StartUpCopy,
FMX.Forms,
frmMain in 'frmMain.pas' {fMain},
MyList in 'MyList.pas',
MyThread in 'MyThread.pas';
{$R *.res}
begin
{$IFDEF DEBUG}
System.ReportMemoryLeaksOnShutdown:=true;
{$ENDIF}
Application.Initialize;
Application.CreateForm(TfMain, fMain);
Application.Run;
end.
Main Form Code:
unit frmMain;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, MyList,
FMX.Controls.Presentation, FMX.StdCtrls;
type
TfMain = class(TForm)
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
FMyList: TMyList;
public
{ Public declarations }
end;
var
fMain: TfMain;
implementation
{$R *.fmx}
procedure TfMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
FreeAndNil(FMyList);
end;
procedure TfMain.FormCreate(Sender: TObject);
begin
FMyList:=TMyList.Create;
end;
end.
MyList Code:
unit MyList;
interface
uses
System.Classes, System.SysUtils, MyThread;
type
TMyList = Class (TObject)
private
FSomeList: TStringList;
FMyThread: TMyThread;
protected
procedure OnMyThreadNotification (Sender: TObject);
procedure OnMyThreadTerminate (Sender: TObject);
procedure ActOnThreadResults (AList: TStringList);
public
procedure InitMyThread;
procedure StopMyThread;
constructor Create;
destructor Destroy; override;
property
SomeList: TStringList read FSomeList;
end;
implementation
{ TMyList }
constructor TMyList.Create;
begin
inherited Create;
FSomeList:=TStringList.Create;
InitMyThread;
end;
destructor TMyList.Destroy;
begin
StopMyThread;
FreeAndNil(FSomeList);
inherited Destroy;
end;
procedure TMyList.ActOnThreadResults (AList: TStringList);
var
i: Integer;
begin
for i:= 0 to AList.Count-1 do
begin
if FMyThread.CheckTerminated then
exit;
FSomeList.Add(AList.Strings[i]);
end;
end;
procedure TMyList.InitMyThread;
begin
FMyThread:=TMyThread.Create (True);
FMyThread.NotifyEvent:=OnMyThreadNotification;
FMyThread.OnTerminate:=OnMyThreadTerminate;
FMyThread.Start;
end;
procedure TMyList.OnMyThreadNotification(Sender: TObject);
var
fullList: TStringList;
begin
if (FMyThread.FList4.Count>0) or (FMyThread.FList5.Count>0) then
begin
fullList:=TStringList.Create;
try
fullList.Text:=FMyThread.FList4.Text + FMyThread.FList5.Text;
ActOnThreadResults(fullList);
finally
FreeAndNil (fullList);
end;
end;
end;
procedure TMyList.OnMyThreadTerminate(Sender: TObject);
begin
FreeAndNil(FMyThread);
end;
procedure TMyList.StopMyThread;
begin
FMyThread.Terminate;
end;
end.
MyThread Code:
unit MyThread;
interface
uses
System.Classes, System.SysUtils;
type
TMyThread = Class (TThread)
private
FLastRun: TDateTime;
FList1: TStringList;
FList2: TStringList;
procedure SomeProcess;
procedure SomeOtherProcess;
protected
procedure Execute; override;
public
NotifyEvent: TNotifyEvent;
FList3: TStringList;
FList4: TStringList;
FList5: TStringList;
destructor Destroy; override;
End;
implementation
destructor TMyThread.Destroy;
begin
FreeAndNil(FList1);
FreeAndNil(FList2);
FreeAndNil(FList3);
FreeAndNil(FList4);
FreeAndNil(FList5);
inherited;
end;
procedure TMyThread.SomeOtherProcess;
var i: integer;
begin
for i := 1 to 1000000 do
begin
if Terminated then
break;
//do some stuff here
FList5.Add(i.ToString);
end;
end;
procedure TMyThread.SomeProcess;
var i: integer;
begin
for i := 1 to 1000000 do
begin
if Terminated then
break;
//do some stuff here
FList4.Add(i.ToString);
end;
end;
procedure TMyThread.Execute;
var
boolCheck: Boolean;
begin
NameThreadForDebugging('Thread with issues');
FreeOnTerminate:=False;
FList1:=TStringList.Create;
FList2:=TStringList.Create;
FList3:=TStringList.Create;
FList4:=TStringList.Create;
FList5:=TStringList.Create;
FLastRun:=Now; //i get this from an ini file normally
try
while Not Terminated do
begin
if Terminated then
Break;
FList1.Clear;
FList2.Clear;
FList3.Clear;
FList4.Clear;
FList5.Clear;
if Terminated then
Break;
SomeProcess;
if Terminated then
Break;
SomeOtherProcess;
if Terminated then
Break;
SomeProcess;
if Terminated then
Break;
SomeOtherProcess;
if Terminated then
Break;
SomeProcess;
if Terminated then
Break;
if (FList4.Count>0) OR (FList5.Count>0) then
boolCheck:=True;
if Terminated then
Break;
if boolCheck then
NotifyEvent(Self);
if Terminated then
Break;
Sleep (2000);
if Terminated then
Break;
FLastRun:=Now; //i save to ini file as well
end;
finally
//i save to ini file the last run
FreeAndNil(FList1);
FreeAndNil(FList2);
FreeAndNil(FList3);
FreeAndNil(FList4);
FreeAndNil(FList5);
end;
end;
end.
You have a race hazard. If you destroy your TMyList object before the thread terminates naturally, it can still call the notify event, but your object no longer exists (even though the thread still does). The simplest way I found to deal with this and stop memory leaks is to wait for the thread to terminate in the StopMyThread routine, and put the destruction of FMyThread into the destructor (as it stands OnTerminate is not called if you exit the application).
destructor TMyList.Destroy;
begin
StopMyThread;
FreeAndNil(FSomeList);
FreeAndNil(FMyThread);
inherited Destroy;
end;
and
procedure TMyList.StopMyThread;
begin
FMyThread.Terminate;
FMyThread.WaitFor;
end;
I am struggling with service application in Delphi but no major success so far. I tried to recreate this project, but it doesn't seem to work properly. File is created, but date and time aren't added to file every 10 seconds. I also don't see a message popping up from my ShowMessage. I successfully install and start service application.
Here is my code:
unit TMS;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.SvcMgr, Vcl.Dialogs,
Vcl.ExtCtrls;
type
TWorkflow = class(TService)
Timer1: TTimer;
procedure ServiceExecute(Sender: TService);
procedure Timer1Timer(Sender: TObject);
procedure ServiceBeforeInstall(Sender: TService);
private
{ Private declarations }
public
function GetServiceController: TServiceController; override;
{ Public declarations }
end;
var
Workflow: TWorkflow;
implementation
{$R *.dfm}
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
Workflow.Controller(CtrlCode);
end;
function TWorkflow.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;
procedure TWorkflow.ServiceBeforeInstall(Sender: TService);
begin
Interactive := True;
end;
procedure TWorkflow.ServiceExecute(Sender: TService);
begin
while not Terminated do
begin
ServiceThread.ProcessRequests(True);
end;
end;
procedure TWorkflow.Timer1Timer(Sender: TObject);
const
FileName = 'D:\Projekti\EMBRACADERO\TMSWorkflow\Win32\Debug\Log.txt';
var
F : TextFile;
begin
AssignFile(F, FileName);
if FileExists(FileName) then
Append(F)
else
Rewrite(F);
Writeln(F, DateTimeToStr(Now), ' ', DiskFree(0));
ShowMessage(DateTimeToStr(Now));
CloseFile(F);
end;
end.
Could somebody give me an example of a service application with threads maybe or service with visual components included?
UPDATE1:
It is working with following code for inserting some data in database every 3 seconds.
private
thread : TThread;
procedure TWorkflow.InsertInDatabase;
begin
FDTransaction1.StartTransaction;
try
FDQuery1.Execute;
FDTransaction1.Commit;
except
FDTransaction1.Rollback;
end;
end;
procedure TWorkflow.ServiceExecute(Sender: TService);
begin
while not Terminated do
begin
ServiceThread.ProcessRequests(False);
InsertInDatabase();
thread.sleep(3000);
end;
end;
procedure TWorkflow.ServiceStart(Sender: TService; var Started: Boolean);
begin
thread := TThread.Create;
end;
procedure TWorkflow.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
FreeAndNil(thread);
end;
The TTimer code you showed is fine (though your OnExecute event is redundant can should be completely removed), except for the call to ShowMessage(), which you cannot use in a service at all (the TService.Interactive property has no effect on Windows Vista+). If you must display a popup message box from a service (which you should strive not to), you must use the Win32 API MessageBox() with the MB_SERVICE_NOTIFICATION flag specified, or use WTSSendMessage() instead. Otherwise, you have to delegate any UI to a separate non-service process that the service spawns and/or communicates with as needed.
Your TThread code, on the other hand, is completely wrong. It should be more like this instead:
unit TMS;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes, Vcl.SvcMgr;
type
TWorkflowThread = class(TThread)
protected
procedure Execute; override;
end;
TWorkflow = class(TService)
FDTransaction1: TFDTransaction;
FDQuery1: TFDQuery;
procedure ServiceStart(Sender: TService; var Started: Boolean);
procedure ServiceStop(Sender: TService; var Stopped: Boolean);
procedure ServiceShutdown(Sender: TService);
private
{ Private declarations }
thread: TWorkflowThread;
procedure InsertInFile;
procedure InsertInDatabase;
public
function GetServiceController: TServiceController; override;
{ Public declarations }
end;
var
Workflow: TWorkflow;
implementation
{$R *.dfm}
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
Workflow.Controller(CtrlCode);
end;
function TWorkflow.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;
procedure TWorkflow.InsertInFile;
const
FileName = 'D:\Projekti\EMBRACADERO\TMSWorkflow\Win32\Debug\Log.txt';
var
F : TextFile;
begin
try
AssignFile(F, FileName);
try
if FileExists(FileName) then
Append(F)
else
Rewrite(F);
Writeln(F, DateTimeToStr(Now), ' ', DiskFree(0));
//ShowMessage(DateTimeToStr(Now));
finallly
CloseFile(F);
end;
except
end;
end;
procedure TWorkflow.InsertInDatabase;
begin
try
FDTransaction1.StartTransaction;
try
FDQuery1.Execute;
FDTransaction1.Commit;
except
FDTransaction1.Rollback;
end;
except
end;
end;
procedure TWorkflow.ServiceStart(Sender: TService; var Started: Boolean);
begin
thread := TWorkflowThread.Create(False);
Started := True;
end;
procedure TWorkflow.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
ServiceShutdown(Sender);
Stopped := True;
end;
procedure TWorkflow.ServiceShutdown(Sender: TService);
begin
if Assigned(thread) then
begin
thread.Terminate;
while WaitForSingleObject(thread.Handle, WaitHint-100) = WAIT_TIMEOUT do
ReportStatus;
FreeAndNil(thread);
end;
end;
procedure TWorkflowThread.Execute;
begin
while not Terminated do
begin
Workflow.InsertInFile;
Workflow.InsertInDatabase;
TThread.Sleep(3000);
end;
end;
end.
Your timer code will not execute because timers rely on a window handle and message pump that TService does not provide. Furthermore, TTimer is not thread-safe because in uses the VCL's AllocateHwnd() function which is not thread-safe and should not be used outside the context of the main thread. Generally, when writing service applications you would spawn a worker thread to perform the main logic.
If you need a thread-safe timer, I would suggest you use a different timer mechanism, such as WaitForSingleObject()
Assitionally, services should not contain any visual controls as they should not interact with the desktop at all.
Could somebody give me an example of service application with threads.
If your code is doing all its work in a thread, you are almost done.
Just start your thread in the service start event. For debugging, run the thread in a small (console) program.
Instead of a timer, let your main thread sleep for a while.
I wrote a simple component that monitors a folder and triggers an event when it detects changes. It works well... apparently. But I'm not sure of one thing. From time to time, the main thread may need to update the monitored path and I'm not sure if I've done this right. It is about the SetNewPath procedure. This is executed from the main thread and it changes the UpdatePath variable from the other thread. It is possible to create an conflict when the main thread writes to UpdatePath and the component thread tries to read its value in the Execute cycle ?
FolderMonitor.pas
unit FolderMonitor;
interface
uses
SysUtils, Windows, Classes, ExtCtrls;
type
TOnFolderChange = procedure(Sender: TObject) of object;
TFolderMonitor = class(TThread)
private
MainWait: THandle;
UpdatePath: Boolean;
TimeOut: Cardinal;
FPath: String;
FOnFolderChange: TOnFolderChange;
procedure DoOnFolderChange;
procedure SetNewPath(Path:String);
protected
procedure Execute; override;
public
constructor Create(const FolderPath: String; OnFolderChangeHandler: TOnFolderChange);
destructor Destroy; override;
procedure Unblock;
property Path: String read FPath write SetNewPath;
property OnFolderChange: TOnFolderChange read FOnFolderChange write FOnFolderChange;
end;
implementation
constructor TFolderMonitor.Create(const FolderPath: String; OnFolderChangeHandler: TOnFolderChange);
begin
inherited Create(True);
FOnFolderChange:=OnFolderChangeHandler;
FPath:=FolderPath;
UpdatePath:=false;
FreeOnTerminate:=false;
MainWait:=CreateEvent(nil,true,false,nil);
Resume;
end;
destructor TFolderMonitor.Destroy;
begin
CloseHandle(MainWait);
inherited;
end;
procedure TFolderMonitor.DoOnFolderChange;
begin
if Assigned(FOnFolderChange) then
Synchronize(procedure
begin
FOnFolderChange(Self);
end);
end;
procedure TFolderMonitor.Unblock;
begin
PulseEvent(MainWait);
end;
procedure TFolderMonitor.SetNewPath(Path:String);
begin
FPath:=Path;
UpdatePath:=true;
PulseEvent(MainWait);
end;
procedure TFolderMonitor.Execute;
var Filter,WaitResult: Cardinal;
WaitHandles: array[0..1] of THandle;
begin
Filter:=FILE_NOTIFY_CHANGE_DIR_NAME + FILE_NOTIFY_CHANGE_FILE_NAME + FILE_NOTIFY_CHANGE_SIZE;
WaitHandles[0]:=MainWait;
WaitHandles[1]:=FindFirstChangeNotification(PWideChar(FPath),false,Filter);
TimeOut:=INFINITE;
while not Terminated do begin
if UpdatePath then begin
if WaitHandles[1]<>INVALID_HANDLE_VALUE then FindCloseChangeNotification(WaitHandles[1]);
WaitHandles[1]:=FindFirstChangeNotification(PWideChar(FPath),false,Filter);
TimeOut:=INFINITE;
UpdatePath:=false;
end;
if WaitHandles[1] = INVALID_HANDLE_VALUE
then WaitResult:=WaitForSingleObject(WaitHandles[0],INFINITE)
else WaitResult:=WaitForMultipleObjects(2,#WaitHandles,false,TimeOut);
case WaitResult of
WAIT_OBJECT_0: Continue;
WAIT_OBJECT_0+1: TimeOut:=200;
WAIT_TIMEOUT: begin DoOnFolderChange; TimeOut:=INFINITE; end;
end;
if WaitHandles[1] <> INVALID_HANDLE_VALUE then
FindNextChangeNotification(WaitHandles[1]);
end;
if WaitHandles[1] <> INVALID_HANDLE_VALUE then
FindCloseChangeNotification(WaitHandles[1]);
end;
end.
UnitMain.pas
unit UnitMain;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, FolderMonitor;
type
TForm1 = class(TForm)
Memo1: TMemo;
Edit1: TEdit;
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure OnFolderChange(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button1Click(Sender: TObject);
end;
var
Form1: TForm1;
Mon: TFolderMonitor;
X: integer;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
X:=0;
Mon:=TFolderMonitor.Create('D:\Test',OnFolderChange);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
Mon.Terminate;
Mon.Unblock;
Mon.WaitFor;
Mon.Free;
end;
procedure TForm1.OnFolderChange(Sender: TObject);
begin
inc(x);
Memo1.Lines.Add('changed! '+IntToStr(x));
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Mon.Path:=Edit1.Text;
end;
end.
You have a variable shared between multiple threads, with one thread modifying the variable. This scenario is known as a data race.
Some races may be benign. This one is not. If one thread modifies the variable whilst another thread reads it, errors may occur. Because the data type is complex (pointer to heap allocated array of characters) it is quite possible for the reading thread to attempt to read from deallocated memory.
For a complex type like this you need to use a mutual exclusion lock whenever you access the value. All reads and writes must be serialised by the lock. Use a critical section or a monitor.
To ensure that you don't ever perform unprotected access it is wise to enforce this rule in code. For example, my TThreadSafe<T> described here: Generic Threadsafe Property
I've been trying to to set the FreeOnTerminate property in the OnTerminate procedure but it seems like it's either too late to set it or it's completely ignoring the write procedure.
How can I set/change the FreeOnTerminate property in the OnTerminate procedure?
Are there any workarounds for that?
A little code:
unit Unit2;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm2 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
procedure OnTestThreadTerminate (Sender : TObject);
public
{ Public declarations }
end;
type
TTestThread = class (TThread)
public
procedure Execute; override;
end;
var
Form2: TForm2;
GlobalThreadTest : TTestThread;
implementation
{$R *.dfm}
procedure TForm2.Button1Click(Sender: TObject);
begin
GlobalThreadTest := TTestThread.Create (True);
GlobalThreadTest.OnTerminate := Self.OnTestThreadTerminate;
GlobalThreadTest.FreeOnTerminate := True;
GlobalThreadTest.Resume;
end;
procedure TForm2.Button2Click(Sender: TObject);
begin
// 2nd Button to try to free the thread...
// AFTER BUTTON1 has been clicked!
try
GlobalThreadTest.Free;
except
on e : exception do begin
MessageBox(Self.Handle, pchar(e.Message), pchar(e.ClassName), 64);
end;
end;
end;
procedure TForm2.OnTestThreadTerminate(Sender: TObject);
begin
TTestThread(Sender).FreeOnTerminate := False; // Avoid freeing...
ShowMessage (BoolToStr (TTestThread(Sender).FreeOnTerminate, True)); // FreeOnTerminate Value has been changed successfully!
end;
procedure TTestThread.Execute;
begin
// No code needed for test purposes.
end;
end.
FreeOnTerminate is evaluated after Execute() exits but before DoTerminate() is called to trigger the OnTerminate event. You can change FreeOnTerminate until Execute() exits, then it is too late. So a workaround would be to trigger OnTerminate manually from inside of Execute(), eg:
type
TTestThread = class (TThread)
public
procedure Execute; override;
procedure DoTerminate; override;
end;
procedure TTestThread.Execute;
begin
try
...
finally
// trigger OnTerminate here...
inherited DoTerminate;
end;
end;
procedure TTestThread.DoTerminate;
begin
// prevent TThread from triggering OnTerminate
// again by not calling inherited here...
end;
The only gotcha is that if Terminate() is called before Execute() is called then TThread will skip Execute(), but it will still call the overridden DoTerminate().
If you take a look to the sources of ThreadProc in Classes.pas, you will find that FreeOnTerminate is evaluated to a local variable Freethread before calling the OnTerminate event in DoTerminate.
After calling DoTerminate the thread is freed depending of the now outdated variable: if FreeThread then Thread.Free;.
You could start the thread without FreeOnTerminate and use PostMessage with an own message e.g. WM_MyKillMessage (WM_APP + 1234) called in OnTerminate to free the thread after leaving the OnTerminate event.
This could look like:
const
WM_KillThread = WM_APP + 1234;
type
TTestThread = class (TThread)
public
procedure Execute; override;
Destructor Destroy;override;
end;
TForm2 = class(TForm)
...............
public
{ Public-Deklarationen }
procedure WMKILLTHREAD(var Msg:TMessage);message WM_KillThread;
end;
procedure TForm2.OnTestThread(Sender: TObject);
begin
ShowMessage ('OnTestThread');
PostMessage(handle,WM_KillThread, WPARAM(Sender), 0);
end;
procedure TForm2.WMKILLTHREAD(var Msg: TMessage);
begin
TTestThread(Msg.WParam).Free;
end;
destructor TTestThread.Destroy;
begin
ShowMessage ('Destroy');
inherited;
end;
Presumably, you want to set FreeOnTerminate False in some sort of condition, but let it otherwise stay True. If that condition by any chance depends on whether its termination is natural (Execute ended normally without intervention) or manual (Terminate is called), then I suggest you do the exact opposite: create the thread with FreeOnTerminate = False and set it True when the Terminated property is False:
procedure TTestThread.Execute;
begin
...
if not Terminated then
FreeOnTerminate := True;
end;
See its functioning for example in When to free a Thread manually.
i have developed application for read information from card reader. Here i have used timer for get the information each five second, so every five second the user interface getting slow
because it's get the information from reader. how to run the timer in background with out affecting user interface
unit frmVistorreg;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms;
type
thread1=class(TThread)
private
FEvent: THandle;
protected
procedure Execute; override;
public
procedure MyTerminate;
end;
TForm3 = class(TForm)
txt_name: TEdit;
txt_cardno.Text TEdit;
private
public
end;
var
Form3: TForm3;
implementation
{$R *.dfm}
procedure thread1.Execute;
var
idcard_info :array[0..1024*5] of byte;
flag :Integer;
portflag :Integer;
st :TStrings;
str :string;
begin
FEvent:= CreateEvent(nil, False, false, nil);
try
while not Terminated do begin
if MainForm.PortFlag=0 then
begin
Form3.Label11.Caption:='port has been successfully opened';
Form3.Label11.Font.Color :=32768;
flag := GetIdCardInfo(#idcard_info[0],1024*5,5);
str := byteArray2Str(#idcard_info[0],1024*5);
if(flag=0) then
begin
st := TStringList.Create;
try
SplitStr('^_^',str,st);
Form3.txt_name.Text := st.Strings[0];
Form3.txt_cardno.Text := st.Strings[5];
finally
st.Free;
end;
end;
end
else
begin
Form3.Label11.Caption:='Please open the port';
Form3.Label11.Font.Color:=clRed;
end;
if WaitForSingleObject(FEvent, 500) <> WAIT_TIMEOUT // 5 seconds timeout
then Terminate;
end;
finally
CloseHandle(FEvent);
end;
end;
procedure thread1.MyTerminate;
begin
SetEvent(FEvent);
end;
procedure TForm3.FormClose(Sender: TObject; var Action: TCloseAction);
var
Objthread1:thread1;
begin
Objthread1.MyTerminate;
Action := caFree;
end;
procedure TForm3.FormCreate(Sender: TObject);
var
Objthread1:thread1;
begin
Objthread1:=thread1.Create(false);
end;
end.
when i close the form have error like
Project MDIAPP.exe raised exception class EAccessViolation with message 'Access violation at address 0051B9F1 in module 'MDIAPP.exe'. Read of address 00000198'.
how can i solve this.
You need not a timer component for that, you need a background thread. A simplest solution is to use Sleep function in the thread:
unit Unit2;
interface
uses
Classes;
type
TMyThread = class(TThread)
protected
procedure Execute; override;
end;
implementation
procedure TMyThread.Execute;
begin
while not Terminated do begin
// do your processing here
Sleep(5000); // wait 5 seconds
end;
end;
end.
A better approach is to use WaitForSingleObject and an event instead of Sleep to be able to terminate your background thread immediately without 5 seconds delay:
unit Unit2;
interface
uses
Windows, Classes;
type
TMyThread = class(TThread)
private
FEvent: THandle;
protected
procedure Execute; override;
public
procedure MyTerminate;
end;
implementation
procedure TMyThread.Execute;
begin
FEvent:= CreateEvent(nil, False, False, nil);
try
while not Terminated do begin
// do your processing here
// ..
if WaitForSingleObject(FEvent, 5000) <> WAIT_TIMEOUT // 5 seconds timeout
then Terminate;
end;
finally
CloseHandle(FEvent);
end;
end;
procedure TMyThread.MyTerminate;
begin
SetEvent(FEvent);
end;
end.
To terminate TMyThread instance on closing a form call MyTerminate method from OnClose event handler of a form.
And yes, it is interesting to know what error message you receive, not just 'showing error'.