Delphi : thread safely suspend in excute module - delphi

I am a beginner of DELPHI.
I have to develop a application based on thread.
How can I suspend thread in thread execute function, not in UI thread....
Please give me sample

Suspend a thread simply by pausing. For instance you can sleep for a specified amount of time. Or you can wait on a synchronisation object. For instance, you might wait on an event. When another thread signals that event, the paused thread will resume.

Assuming you are working on Windows platform...
There are many ways to suspend a thread. Which one to use depends largely on when/why it should resume, and whether or not the thread has a message queue.
The point about the message queue is especially important if you need to suspend a thread for a long time. Any thread not processing Windows message can hang many operations, DDE communications, message broadcast, etc. The contract being "If your thread has a message queue, it NEEDS to treat them.", and it needs to do so in a timely fashion. In this case, I would suggest calling MsgWaitForMultipleObjects from the thread. The function works even if you are not waiting on any objects, which allows you to both wait on messages and have a timeout. WaitMessage could work too, but it doesn't timeout which has, amongst other implication, that you would need to send a message to the thread after terminating it, otherwise, it might never terminate.
If the thread does not have a message queue, then there are plenty of options. Sleep is a valid one if you want to wait for a specific amount of time. If you want to wait for a specific event to resume the thread, the TEvent class might be what you are looking for.
Hard to give a definitive answer without more details.

This example runs OK. By working with threads, by very careful. This is a very difficult section.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TMyThread=class(tthread)
FEvent:THandle;
private
procedure SetText;
protected
procedure Execute;override;
public
constructor Create(CreateSuspended:Boolean=false);
destructor Destroy; override;
procedure Start;
procedure Stop;
procedure StopAndTerminate;
end;
type
TForm1 = class(TForm)
OutMem: TMemo;
BtnStart: TButton;
BtnStop: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure BtnStartClick(Sender: TObject);
procedure BtnStopClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
mythread:tmythread;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
mythread:=tmythread.Create();
end;
{ TMyThread }
constructor TMyThread.Create(CreateSuspended: Boolean);
begin
Inherited Create(CreateSuspended);
FEvent:=CreateEvent(nil,true,false,pchar(inttostr(handle)));
end;
destructor TMyThread.Destroy;
begin
CloseHandle(FEVENT);
inherited;
end;
procedure TMyThread.Execute;
begin
while not terminated do
begin
WaitForSingleObject(FEvent,INFINITE);
if terminated then exit;
Synchronize(SetText);
{ Methods and properties of objects in visual components (forms, buttons, memo and other)
can only be used in a method called using Synchronize}
end;
end;
procedure TMyThread.SetText;
begin
form1.OutMem.Lines.Append(inttostr(gettickcount));
application.ProcessMessages;
// Application.ProcessMessages method must be call ONLY in MainThread.
// by using Synchronize, SetText procedure will be run in MainThread.
end;
procedure TMyThread.Start;
begin
SetEvent(FEVENT);
end;
procedure TMyThread.Stop;
begin
ResetEvent(FEVENT);
end;
procedure TMyThread.StopAndTerminate;
begin
Terminate;
PulseEvent(FEVENT);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
mythread.StopAndTerminate;
mythread.WaitFor;
mythread.Free;
end;
procedure TForm1.BtnStartClick(Sender: TObject);
begin
mythread.Start;
end;
procedure TForm1.BtnStopClick(Sender: TObject);
begin
mythread.Stop;
end;
end.

Related

Delphi while loop causing program to stop responding

I'm using Delphi 7 and the program I am writing needs to continuously draw on the screen. While it currently doesn't draw anything important, this is a necessity in the program later on. However, when I put the procedure for drawing the screen in a while loop which can only be stopped by pressing any button the program stops responding completely. I don't understand why this is happening. Surely, as the while loop can be exited, the program should continue to run fine.
Here is the source code:
unit DD04f1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, TeCanvas, ExtCtrls;
type
TForm1 = class(TForm)
Image1: TImage;
Button1: TButton;
procedure Image1OnCreate();
procedure ScreenRender();
procedure OnCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
IsDone : Boolean;
implementation
{$R *.dfm}
procedure TForm1.OnCreate(Sender: TObject);
begin
IsDone := False;
end;
procedure TForm1.Image1OnCreate ();
var
Count:Integer;
begin
image1.canvas.Create();
image1.canvas.Pen.Color:=clBlack;
image1.canvas.rectangle(0,0,640,480);
image1.canvas.Pen.Color:=$ed630e; //bgr instead of rgb
Count:=0;
While (Count <> 640) do
begin
image1.Canvas.moveto(Count,0);
image1.Canvas.LineTo(Count,480);
Count:=Count+1;
end;
end;
procedure TForm1.ScreenRender();
var
Count : Integer;
begin
Count:=0;
While(Count<>640) do
begin
image1.Canvas.moveto(Count,0);
image1.Canvas.LineTo(Count,480);
Count:=Count+1;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Image1OnCreate();
Button1.Visible := False;
While(IsDone = False) do
begin
ScreenRender();
end;
end;
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
IsDone := True;
end;
end.
procedure TForm1.OnCreate(Sender: TObject);
begin
IsDone := False;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Image1OnCreate();
Button1.Visible := False;
While(IsDone = False) do
begin
ScreenRender();
end;
end;
Assuming IsDone is always False (because otherwise we would not enter the loop), this loop can not terminate. It is infinite.
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
IsDone := True;
end;
You do not call this procedure from inside TForm1.Button1Click loop, hence it can never be called after you entered that loop. Since you never exit the TForm1.Button1Click procedure you do not allow any outside agent (like messages dispatch loop in VCL) to get executed and call that procedure either. To sum it up as soon as you entered the loop there is no any executable code that can change IsDone value. So, it is not changed.
Event handlers are supposed to be very short procedures, executing almost immediately, and giving up "execution flow control" back to VCL internals. Every long (more so infinite) processing leads to the program becomes irresponsive. No matter how many news Windows might want to tell the program - the program never asks for them.
https://en.wikipedia.org/wiki/Event-driven_programming
https://msdn.microsoft.com/en-us/library/windows/desktop/ms644927.aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/ms632593.aspx
It was once told that Windows windows (GDI objects) are living in the center of the "messages storm" that they have to work out in timely matter. Hundreds of those messages are incoming every second and a Window Procedure (built inside the VCL classes for Delphi 7 forms) should receive, dispatch, and process every one of them before it's too late.
As soon as you blocked that process by making one of event handlers long or even endless - you broke the basic contract between the OS and the application.
You have to do "inversion of control", to break your continuous work into small short chunks and make Windows call those chunks when it sees appropriate.
Try to use TTimer for example.
PS. A VERY remote problem you can look at:
How to use Pipeline pattern in Delphi
How to Stop all Pipeline tasks correctly
Skip all the multithreading stuff there, for your case it only is important that other threads create those "chunks of work" that we have to paint onto our forms when Windows asks us to do so at some reasonable framerate (not too fast and not too slow). Your work chunks are fundamentally different, so all the threading stuff unrelated to you.
And the rendering is made inside TTimer events. So the "framework" of setting up the timer, turning it on and off might be of some interest to you. However the work you are going to do inside the .OnTimer event would be significantly different (just painting something, or even merely invalidating some part of the form and waiting for the Windows to trigger OnPaint event.).
You already got an excellent answer why your current code does not work and in your comments you are mentioning you want to do ray casting and drawing from a players perspective, so I assume some kind of game background.
I'm not sure the VCL is the best basis for a game. Different philosophies and needs. As Arioch 'The explained Delphi's VCL is event driven. Things happen in response to windows messages, even painting. If nothing causes a need to repaint, nothing will be painted anew.
This is very different from how I understand game engines (I'm by no means an expert). Even if nothing happens, they will continuously draw frame after frame to present as fluid as possible. Each frame might include an update to underlying structures based on game rules, physics, player input, animation, but even when they remain the same a new frame will be drawn. Basically three steps happen in a simplified 'game loop'
Input
Update
Presentation
All this happens for every frame. There might be no input, no update of the game's structures or even no presentation is desired. But all three steps belong together, the input causing an update that is later presented happened in the exact same frame as the resulting drawing.
This is something I find hard to fit into the VCL. As a solution must be based on the existing VCL loop and windows messages. You basically attempted to create such a game loop in VCL.
A way to solve your immediate issue - that you want to present something based on a calculation - could be just using the principle of the VCL. You want to have something drawn. VCL controls normally communicate their desire to be drawn by Invalidate, causing their BoundsRect to be invalidated. You could do that after you have done your calculations. In the following example I'll just use a timer to simulate your calculations are done. Just be aware that Invalidate will cause WM_PAINT messages to be generated for the control, but will not cause immediate repainting. There might be messages queued before the WM_PAINT is processed.
I'm using a TPaintBox's OnPaint to actually do the painting work, you might want to have your own control for that in the future when your project progresses.
unit Unit2;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls;
type
TFormMain = class(TForm)
procedure FormCreate(Sender: TObject);
private
Timer1: TTimer;
PaintBox1: TPaintBox;
{ Private declarations }
procedure PaintBox1Paint(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
public
{ Public declarations }
end;
implementation
{$R *.dfm}
procedure TFormMain.FormCreate(Sender: TObject);
begin
PaintBox1 := TPaintBox.Create(Self);
PaintBox1.Parent := Self;
PaintBox1.Align := alClient;
PaintBox1.OnPaint := PaintBox1Paint;
Timer1 := TTimer.Create(Self);
Timer1.Interval := 100;
Timer1.OnTimer := Timer1Timer;
Randomize;
end;
procedure TFormMain.PaintBox1Paint(Sender: TObject);
var
AColor: TColor;
I: Integer;
begin
for I := 0 to PaintBox1.ClientWidth - 1 do
begin
AColor := RGB(Random(256), Random(256), Random(256));
PaintBox1.Canvas.Pen.Color := AColor;
PaintBox1.Canvas.MoveTo(I, 0);
PaintBox1.Canvas.LineTo(I, PaintBox1.ClientHeight);
end;
end;
procedure TFormMain.Timer1Timer(Sender: TObject);
begin
PaintBox1.Invalidate;
end;
end.

FireDac freezes GUI

I am working with FireDac under Delphi 10.1 Berlin.
For displaying data to the user i use data aware controls like TDBEdit.
I use TFDQuery and TDataSource to link them with the controls.
This works but long sql queries that take some time to exectute will freeze the GUI.
I am wondering how to stop the gui from freezing while performing those long running queries.
I was thinking about background threads.
On the wiki i read that FireDac can work with multithreads:
http://docwiki.embarcadero.com/RADStudio/XE6/en/Multithreading_(FireDAC)
However in embarcadero community forums thread Jeff Overcash writes:
One thing I didn't see asked or Dmitry mention is you can not have
TDataSource or LiveBindings against your background threaded queries.
If you are background threading a query that displays the results you
should disconnect the LB or DataSource, open and fetch all the data
then re establish the connection.
Those two will be trying to move the cursor on you or querying the
buffer for display while the buffer is very volatile being moved
around in a different thread.
I am wondering if someone that also uses FireDac and displays the values on a form can help me out here.
The code sample below shows one way to retrive records from an MSSql Server
in a background thread using FireDAC. This omits a few details. For example, in practice, rather than the TQueryThreads Execute opening the query only once and then terminating, you would probably want the thread's Execute to contain a while loop in which it waits on a semaphore after the call to Synchronize and then close/re-open the query to update the main thread as often as you want.
type
TForm1 = class;
TQueryThread = class(TThread)
private
FConnection: TFDConnection;
FQuery: TFDQuery;
FForm: TForm1;
published
constructor Create(AForm : TForm1);
destructor Destroy; override;
procedure Execute; override;
procedure TransferData;
property Query : TFDQuery read FQuery;
property Connection : TFDConnection read FConnection;
property Form : TForm1 read FForm;
end;
TForm1 = class(TForm)
FDConnection1: TFDConnection;
FDQuery1: TFDQuery;
DataSource1: TDataSource;
DBGrid1: TDBGrid;
DBNavigator1: TDBNavigator;
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
public
QueryThread : TQueryThread;
end;
[...]
constructor TQueryThread.Create(AForm : TForm1);
begin
inherited Create(True);
FreeOnTerminate := True;
FForm := AForm;
FConnection := TFDConnection.Create(Nil);
FConnection.Params.Assign(Form.FDConnection1.Params);
FConnection.LoginPrompt := False;
FQuery := TFDQuery.Create(Nil);
FQuery.Connection := Connection;
FQuery.SQL.Text := Form.FDQuery1.SQL.Text;
end;
destructor TQueryThread.Destroy;
begin
FQuery.Free;
FConnection.Free;
inherited;
end;
procedure TQueryThread.Execute;
begin
Query.Open;
Synchronize(TransferData);
end;
procedure TQueryThread.TransferData;
begin
Form.FDQuery1.DisableControls;
Form.FDQuery1.Data := Query.Data;
Form.FDQuery1.EnableControls;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
QueryThread.Resume;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
QueryThread := TQueryThread.Create(Self);
end;
MJN's comment about bookmarks tells you how to preserve the current data row position in the gui.
Btw, although I've often done this with TClientDataSets, putting this answer together was the first time I'd tried it with FireDAC. In terms of configuring the components, all I did was to drag the components off the Palette, "wire them together" as you'd expect and then set the FDConnection's Params and the FDQuery's Sql.

Why won't TTimer work in OTL worker task?

I wanted to realize a repetitive task in an OmniThreadLibrary worker task, that runs in another thread. The task should be executed every 3 seconds, for example.
Therefore I wrote a TOmniWorker descendant with an instance of TTimer as you can see below:
program Project14;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
Vcl.ExtCtrls,
Vcl.Forms,
OtlTaskControl;
type
TMyTimerWorker = class(TOmniWorker)
strict private
FTimer: TTimer;
procedure DoOnTimer(Sender: TObject);
protected
function Initialize: Boolean; override;
procedure Cleanup; override;
end;
{ TMyTimerWorker }
procedure TMyTimerWorker.Cleanup;
begin
FTimer.Free;
inherited;
end;
procedure TMyTimerWorker.DoOnTimer(Sender: TObject);
begin
Beep;
end;
function TMyTimerWorker.Initialize: Boolean;
begin
Result := inherited;
if not Result then exit;
FTimer := TTimer.Create(nil);
FTimer.OnTimer := DoOnTimer;
FTimer.Interval := 3000;
FTimer.Enabled := True; // note: this isn't necessary, but is added to avoid hints that 'Enabled' might be 'False'
end;
var
LTimerWorker: IOmniWorker;
begin
try
LTimerWorker := TMyTimerWorker.Create;
CreateTask(LTimerWorker).Unobserved.Run;
while True do
Application.ProcessMessages;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
I set breakpoints in Initialize and DoOnTimer. Former executes well but latter won't be called at all. BTW: Cleanup isn't called neither, so the task is still running.
What am I doing wrong? Is it impossible to use a TTimer in an OTL task? If yes, why?
UPDATE: I found a workaround for TTimer () but why does TTimer approach not work?
You TTimer-based code doesn't work because TTimer uses windows messages to trigger the timer event and windows messages are not processed in an OTL worker by default.
Call .MsgWait before .Run and internal worker loop will use MsgWaitForMultipleObjects instead of WaitForMultipleObjects which will allow for message processing.
Saying that, you really should not use TTimer in background tasks because - as others have said - TTimer is not threadsafe.

Proper Catastrophic Error Handling

There's something I keep running into that I really haven't solved with Delphi programs and was wondering if anyone could instruct me on it. As the topic says, how do you do proper catastrophic error handling? For instance:
// is file necessary for the program present?
if not FileExists(FilePath1) then
begin
raise Exception.Create(FilePath1 + ' does not exist and is required for this program to function.');
// I obviously need to do something here to make the program QUIT and not have
// any more code run.
Application.Terminate;
Abort;
end;
I can use the exception unit there as well and throw out an exception, but the program continues as before. I've used the halt call in the past, but it seems to not do any cleanup or the like so I end up making a big procedure with close and free calls for everything I've done just to be sure (and even then I'm not sure of any of the behind the scenes stuff).
So what is the right way to handle such things?
Edit: To clarify, I'm wanting to know about how to make the program do what clean-up it needs to do and then EXIT NOW and not do any other code.
To perform abnormal termination call Halt() passing the exit code.
if CatastropicErrorDetected then
begin
... show error message
Halt(1);
end;
On Windows this results in a call to TerminateProcess and will stop execution there and then.
You note that no cleanup is performed and usually that's what you want. Since you are performing this check at application startup there should be nothing to cleanup.
IMHO the only clean way would be checking for "Fatal conditions" before Application is running.
program Project2;
uses
Forms,Dialogs,
Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
begin
ReportMemoryLeaksOnShutDown := true;
Application.Initialize;
if True then // your condition here
begin
MessageDLG('Fatal Error',mtError,[mbok],0);
end
else
begin
Application.CreateForm(TForm1, Form1);
Application.Run;
end;
end.
Any other approach will have side effects
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private-Deklarationen }
FSL:TStringList;
public
Destructor Destroy;override;
{ Public-Deklarationen }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
destructor TForm1.Destroy;
begin
FreeAndNil(FSL);
Showmessage('Will never be seen with Application.Terminate + HALT');
inherited;
end;
procedure TForm1.FormCreate(Sender: TObject);
const
Testing=0; // try 1 and 2 too
begin
FSL:=TStringList.Create;
Try
raise Exception.Create('Terminating now');
except
case Testing of
0: begin
// exception object will not be freed other code will be prevented, Form won't be shown
Application.Terminate;
HALT;
end;
1: begin
// exception object will not be freed Form won't be shown
HALT;
end;
2: begin
// clean but Form will be shown
Application.Terminate;
end;
end;
end;
end;
end.
You can instruct the application global object to terminate the program by calling Application.Terminate.
Call Terminate to end the application programmatically. By calling Terminate rather than freeing the application object, you allow the application to shut down in an orderly fashion.
Terminate calls the Windows API PostQuitMessage function to perform an orderly shutdown of the application. Terminate is not immediate.
Since the call can occur deeper in the stack, you can also raise an Exception, and you code your program to not handle it in order to let the execution reach the main application loop and the default exception handler catch it.
That way, you effectively prevent's more code to run in your application.
In code it may look like this:
if not FileExists(FilePath1) then
begin
MessageDlg(FilePath1 + ' does not exist and is required for this program to function.',
mtWarning, [mbOK], 0);
Application.Terminate;
Abort; //raising a EAbort just as an example
end;
Depending on where this code is called, I advise you not to show the message directly, but rather raise an exception with the message and let the application object default HandleException method show the message for you:
if not FileExists(FilePath1) then
begin
Application.Terminate;
raise EMyFatalException.Create(FilePath1
+ ' does not exist and is required for this program to function.');
end;
Which looks more natural to me. EMyFatalException is a hypothetical exception class you can declare and never handle in your except clauses.
You can write your own Application.OnException handler, ex:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
procedure HandleException(Sender: TObject; E: Exception);
end;
var
Form1: TForm1;
type
EMyFatalError = class(Exception);
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
Application.OnException:= HandleException;
raise EMyFatalError.Create('OOPS!');
end;
procedure TForm1.HandleException(Sender: TObject; E: Exception);
begin
Application.ShowException(E);
if E is EMyFatalError then
Application.Terminate;
end;
end.

Indy10 Deadlock at TCPServer

to write information on the processing state to the GUI inside a tcpserver.onexecute(..) function , i used the following command sequence
ExecuteDUMMYCommand(Global_Send_Record);
BitMap_PaintImageProcess;
TThread.Synchronize(nil, BitMap_PaintImageProcess);
The code is working well on some machines, but on a few it fails. The code execution stops atTThread.Synchronize command. I guess on these machines the function call is trapped inside a deadlock
Any chance to figure out the real problem behind ?
The procedure BitMap_PaintImageProcess , here I create a Bitmap and do a lot of painting stuff , but is seems that this code is never executed ?
I try to explain the very long code and reduce to the main points , the critical thread issues are hidden in processing the bitmap inside my Bitmapprocessingclass.
This class is accessed inside the GUIProcessing procedures of my ServerMainForm which also has the INDY TCP Server component.
{--------------- CLASS DEFINITION -----------------------------}
TBitMapProcessingClass = class()
FBitmap : TBitmap;
FList : TListOfSomething;
procedure ProcessTheBitmap(....);
......
(many many functions);
procedure Init;
procedure Free;
Procedure Create;
end;
TMainform = class(TForm)
MyServer : TIdTCPServer;
aBitMaoProcessingClass : TBitMaoProcessingClass;
procedure BitMap_PaintImageProcess;
procedure BitMap_ListProcess;
.....
end;
{------------------------- Implemantation ------------------------------}
procedure TMainform.IndyTCPServer.Onexecute()
begin
.......
ExecuteDUMMYCommand(Global_Send_Record);
BitMap_PaintImageProcess;
TThread.Synchronize(nil, BitMap_PaintImageProcess);
.......
end;
procedure TMainform.BitMap_PaintImageProcess;
begin
DoSomeServerVCLStuff(....);
aBitMapProcessingClass.ProcessTheBitmap;
DoSomeServerVCLStuff(....);
end;
Having no idea what BitMap_PaintImageProcess() does in fact, I have a few suppositions:
In the TThread.Synchronize call you try to read some data from the socket/idContext, but the data is not yet available. This will block the main thread until the data becomes available. But Indy's thread that is responsible for reading from the underlying socket buffer is currently blocked by your TThread.Synchronize call in the OnExecute event i.e. deadlock occurs;
In the TThread.Synchronize call you use Application.ProcessMessages (a common mistake);
In the OnExecute event you enter a critical section. Then during the TThread.Synchronize call you try to enter the same critical section again;
You modify the GUI in the onExecute event. Because onExecute is not thread safe, accessing VCL/FM from Indy's thread could lead to unpredictable results, including random deadlocks (hard to find).
I would suggest to use MadExcept / Eurekalog. They both have options to check if the main thread is "frozen". When that happens (during the deadlock) they will show you the current call stack. Having the call stack you can figure out which function is causing the deadlock.
Regarding the posted code:
procedure TMainform.IndyTCPServer.Onexecute()
begin
.......
ExecuteDUMMYCommand(Global_Send_Record);
BitMap_PaintImageProcess; //-> You do VCL stuff in the context of Indy's thread!
TThread.Synchronize(nil, BitMap_PaintImageProcess);
end;
In the BitMap_PaintImageProcess() you call DoSomeServerVCLStuff(....). Do not forget that OnExecute is fired from Indy's thread for the current context. I.e. you modify VCL from another thread (other from the Main Thread) which is not thread safe.
On your comment:
...but here my complex TBitmap processing Class must be alive the whole
time my Server is active...
If you have only one (global) instance for image processing, then what will happen if another client connects, while you are still processing the old connection (think Parallel :) )? Your image processing class should be instantiated separately for each new connection/context. For GUI updating you can use TIdNotify descendant.
A possible solution:
type
{ tIdNotify Stuff }
TVclProc= procedure(imgClass: tMyImageProcessingClass) of object;
tIdNotifyDescendant = (tIdNotify)
protected
fImgClass: tMyImageProcessingClass;
fProc: TVclProc;
procedure DoNotify; override;
public
class procedure updateVcl(imgClass: tMyImageProcessingClass; vclProc: TVclProc);
end;
procedure tIdNotifyDescendant.DoNotify;
begin
inherited DoNotify;
FProc(fImgClass);
end;
class procedure tIdNotifyDescendant.updateVcl(imgClass: tMyImageProcessingClass; vclProc: TVclProc);
begin
with Create do
begin
fImgClass := imgClass;
fProc := vclProc;
Notify;
end;
end;
{ Indy stuff & other logic }
procedure TForm1.IdTCPServer1Connect(AContext: TIdContext);
begin
// Create your instance when the client connects
AContext.Data := tMyImageProcessingClass.Create;
end;
procedure TForm1.IdTCPServer1Disconnect(AContext: TIdContext);
begin
// Do cleanup
if assinged(AContext.Data) then
(AContext.Data as tMyImageProcessingClass).Free // Casting just for clarity
end;
procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
imgProcClass: tMyImageProcessingClass;
begin
imgProcClass := acontext.Data as tMyImageProcessingClass;
// Do image processing
// Notify GUI for the progress:
tIdNotifyDescendant.updateVcl(AContext.data as tMyImageProcessingClass);
end;
Tip: If you do JPEG processing and you use Draw() method have in mind this: TJPEGImage.Draw() is not thread safe
I added a few more details on my BitmapProcessingclass and the idea of a thread safe extension of the existing class...
I need the existing class u nchanged in others apps ... I need a extension inside my app with the indy server.
ONly one client my connect to one server, or he has to query the state of the server
type TBmpNotify = class(TIdNotify)
protected
FBMP: MyImageProcessingClass;
procedure DoNotify; override;
public
constructor Create(aBMP: MyImageProcessingClass);
function SetImageView(LL, UR: TPoint): Boolean;
procedure PaintBitMap;
function InitBitMap(x, y: Integer;
PixelFormat: TPixelFormat = pf24bit): Boolean;
destructor free;
end;
implementation
{ TBmpNotify }
constructor TBmpNotify.Create(aBMP: MyImageProcessingClass);
begin
// indise this class I also create
// class.TBitmap
// class.TList
// much more stuff ....
FBmp := MyImageProcessingClass.Create;
end;
procedure TBmpNotify.DoNotify;
begin
inherited;
end;
destructor TBmpNotify.free;
begin
FBmp.Free;
inherited;
end;
function TBmpNotify.InitBitMap(x, y: Integer;
PixelFormat: TPixelFormat): Boolean;
begin
// Write values to the List
// also modify TBitmap
// execution time of this function ~ 5 min
FBmp.InitBitMap(x,y,PixelFormat)
end;
procedure TBmpNotify.PaintBitMap;
begin
// much TBitmap, bitmap.canvas .... is used
// execution time of this function ~ 1 min
FBmp.PaintBitMap;
end;
function TBmpNotify.SetImageView(LL, UR: TPoint): Boolean;
begin
// this function takes about 1 min
FBmp.SetImageView(LL, UR);
end;
end.

Resources