I am handling some specific TForm's event [CMControlListChanging],
and need to modify that (inserted) control, but thing gets bad, when I try to do so, because apparently it's not meant to do this in the middle of VCL operation.
So I need to defer that control modification, by queuing the code away from the [CMControlListChanging] handler, to be called at later time.
Sure, I can do PostMessage stuff, but I want more general approach.
System.Classes unit contains
class procedure Synchronize(ASyncRec: PSynchronizeRecord; QueueEvent: Boolean = False); overload;
which could do the trick, but it checks, whether
CurrentThread.ThreadID = MainThreadID
and if yes, then call method I try to queue immediately.
Is the any good approach to deferred calls, at least on the main thread?
Not sure if this is what you are looking for, but if you are on a recent Delphi version these Postpone methods may come in handy. They execute AProc in the main thread after applying an optional non-blocking delay.
uses
System.Threading,
System.Classes;
procedure Postpone(AProc: TThreadProcedure; ADelayMS: Cardinal = 0); overload;
begin
TTask.Run(
procedure
begin
if ADelayMS > 0 then begin
TThread.Sleep(ADelayMS);
end;
TThread.Queue(nil, AProc);
end);
end;
procedure Postpone(AProc: TThreadMethod; ADelayMS: Cardinal = 0); overload;
begin
TTask.Run(
procedure
begin
if ADelayMS > 0 then begin
TThread.Sleep(ADelayMS);
end;
TThread.Queue(nil, AProc);
end);
end;
How about this one? It doesn't create a new task, just a new HWND (to receive the message):
TYPE TDelayedProc = REFERENCE TO PROCEDURE;
TYPE
TPostponeClass = CLASS(TWinControl)
CONSTRUCTOR Create(P : TDelayedProc);
STRICT PRIVATE
Proc : TDelayedProc;
PROCEDURE Run(VAR M : TMessage); MESSAGE WM_USER;
END;
CONSTRUCTOR TPostponeClass.Create(P : TDelayedProc);
BEGIN
INHERITED Create(NIL);
Parent:=Application.MainForm;
Proc:=P;
PostMessage(Handle,WM_USER,0,0)
END;
PROCEDURE TPostponeClass.Run(VAR M : TMessage);
BEGIN
Proc;
Free
END;
PROCEDURE Postpone(Proc : TDelayedProc);
BEGIN
TPostponeClass.Create(Proc)
END;
It even works with anonymous methods, so you can postpone a part of execution of an event and still have access to the local variables (and the instance "Self").
Use it like this:
PROCEDURE WriteFile(CONST F : TFileName ; CONST STR : STRING);
VAR
TXT : TextFile;
BEGIN
AssignFile(TXT,F);
IF FileExists(F) THEN APPEND(TXT) ELSE REWRITE(TXT);
WRITELN(TXT,STR);
CloseFile(TXT)
END;
PROCEDURE TForm37.Button1Click(Sender: TObject);
CONST
N = 'LOGFILE.TXT';
VAR
LocalVar : INTEGER;
BEGIN
DeleteFile(N);
LocalVar:=20;
Postpone(PROCEDURE
BEGIN
WriteFile(N,'Postponed Code: '+Name+' ('+IntToStr(LocalVar)+')')
END);
WriteFile(N,'Last statement in event: '+Name)
END;
The LOGFILE.TXT file will contain the two lines:
Last statement in event: Form37
Postponed Code: Form37 (20)
to illustrate that the postponed event is only run at the end of the event, as well as access to local variables.
Caveat: Do you run Application.ProcessMessages between Postpone call and event exit - if you do, the postponed code will be run at that point.
Since Delphi 10.2 Tokyo, it is possible to defer a call from the main thread asynchronously using a method of TThread.
See TThread.ForceQueue:
class procedure ForceQueue(const AThread: TThread; const AMethod: TThreadMethod); overload; static;
class procedure ForceQueue(const AThread: TThread; const AThreadProc: TThreadProcedure); overload; static;
Queues the execution of a method call within the main thread.
Unlike Queue, the execution of the method call specified by AMethod is forced to be queued although it is called by the main thread.
AMethod associates the caller thread:
For static methods, you can associate the AMethod with any thread using the AThread parameter.
You can use nil/NULL as the AThread parameter if you do not need to know the information of the caller thread in the main thread.
RemoveQueuedEvents uses this thread information to find the proper queued method.
Related
I am reading "Delphi High performance" and there is something that I am missing. Given this code as test:
type TTest = class(TThread)
private
amemo: TMemo;
public
constructor Create(ss: boolean; memo: TMemo);
protected
procedure Execute; override;
end;
constructor TTest.Create(ss: boolean; memo: TMemo);
begin
inherited Create(ss);
FreeOnTerminate := true;
amemo := memo;
end;
procedure TTest.Execute;
var i: uint32;
begin
inherited;
i := 0;
while not Terminated do
begin
Inc(i);
Synchronize(procedure
begin amemo.Lines.Add(i.ToString) end);
Sleep(1000);
end;
end;
Very simply, this thread prints some numbers in a memo. I start the thread suspended and so I have to call this piece of code:
procedure TForm1.Button1Click(Sender: TObject);
begin
thread := TTest.Create(true, Memo1);
thread.Start;
end;
I have always stopped the thread calling thread.Terminate; but reading the book I see that Primoz stops a thread like this:
procedure TForm1.Button2Click(Sender: TObject);
begin
thread.Terminate;
thread.WaitFor; //he adds this method call
//FreeAndNil(thread)
//there is the above line as well in the code copied from the book but I have removed it since I have set FreeOnTerminate := true (so I dont have to worry about freeing the obj).
end;
At this point, if I run the code using only Terminate I have no problems. If I run the code with Terminate + WaitFor I get this error:
I have read more coding in delphi too and I see that Nick Hodges just makes a call to Terminate;. Is calling Terminate; enough to safey stop a thread? Note that I've set FreeOnTerminate := true so I don't care about the death of the object. Terminated should stop the execution (what is inside execute) and so it should be like this:
Call Terminated
Execute stops
Thread stops execution
Thread is now free (FreeOnTerminate := true)
Please tell me what I'm missing.
Note.
In the book the thread doesn't have FreeOnTerminate := true. So the thread needs to be freed manually; I guess that this is the reason why he calls
thread.Terminate;
thread.WaitFor;
FreeAndNil(thread)
I agree on Terminate (stop the thread= and FreeAndNil (free the object manually) but the WaitFor?
Please tell me what I'm missing.
The documentation for FreeOnTerminate explicitly says that you cannot use the Thread in any way after Terminate.
That includes your WaitFor call, which would work on a possibly already free'd object. This use-after-free can trigger the error above, among other even more "interesting" behaviours.
I am designing two components that asynchronously receive objects of a custom class (TMELogMessage) and store them in a thread-safe internal container. The first component is non visual (TMEFileLogger) and should write some info from these objects to a log file (non surprisingly). The second component (TMELogGrid) is a visual FMX.Grid descendant that should visualize some info from these objects in the UI. But what they do with these objects is, I think, irrelevant.
The problem I am facing is that these components do not actually know when these objects will be enqueued in their internal container, so they have to check the container themselves to see if there are any new objects that need processing, process them and remove them from the queue. Ideally I'd want this to be done when the application is not too busy, in a way similar to action updating, so as not to bog down the UI.
It is obviously wrong for a component to hook an event handler like Application.OnIdle... I could maybe subscribe to the TIdleMessage, but I'm not sure that is a good idea, as I've read that a some applications could never go idle. Using an internal timer seems a bit old-school. I could maybe use a low priority thread to poll the queue and then synchronize with the UI only when I find some object to process. I don't have other ideas though.
What is the proper way to do this in Delphi + multiplatform FireMonkey?
I don't like to answer my own questions, but I wanted this question to be answered, as it might be helpful to others. While Deltics' answer is useful, that is not the way I decided to go. I followed the advice in Remy's comment and encapsulated everything in a message handling class that components and forms can use. So both the TMEFileLogger and the TMELogGrid now use an instance of this new TMEMessageHandler class.
Here is some interface code to explain what I did. Keep in mind that this is to be a substitute and enhancement of the rtl System.Messaging unit. The problem with the rtl messaging system is that it provides only for sending synchronous messages. I wanted a richer interface. This is what my message manager looks like:
TMEMessageManager = Class
...
Public
...
Procedure PostDelayedEnvelope(Const Envelope: TMEMessageEnvelope; Const DelayMSec: Cardinal; Const ADispose: Boolean = True); Inline;
Procedure PostDelayedMessage(Const Sender: TObject; AMessage: TMessage; Const DelayMSec: Cardinal; Const ADispose: Boolean = True); Inline;
Procedure PostEnvelope(Const Envelope: TMEMessageEnvelope; Const ADispose: Boolean = True); Inline;
Procedure PostMessage(Const Sender: TObject; AMessage: TMessage; Const ADispose: Boolean = True); Inline;
Procedure SendEnvelope(Const Envelope: TMEMessageEnvelope; Const ADispose: Boolean = True); Inline;
Procedure SendMessage(Const Sender: TObject; AMessage: TMessage; Const ADispose: Boolean = True); Inline;
Function Subscribe(Const AMessageClass: TClass; Const AReceiver: IMEEnvelopeReceiver): Integer; Overload;
Function Subscribe(Const AMessageClass: TClass; Const AMethod: TMessageMethod): Integer; Overload; Deprecated 'Use TMEMessageManager.Subscribe(AMessageClass, AReceiver)';
Function Subscribe(Const AMessageClass: TClass; Const AProcedure: TMessageProcedure): Integer; Overload; Deprecated 'Use TMEMessageManager.Subscribe(AMessageClass, AReceiver)';
Procedure Unsubscribe(Const AMessageClass: TClass; ID: Integer; Const Immediate: Boolean = False); Overload;
Procedure Unsubscribe(Const AMessageClass: TClass; Const AReceiver: IMEEnvelopeReceiver; Const Immediate: Boolean = False); Overload;
Procedure Unsubscribe(Const AMessageClass: TClass; Const AMethod: TMessageMethod; Const Immediate: Boolean = False); Overload; Deprecated;
Procedure Unsubscribe(Const AMessageClass: TClass; Const AProcedure: TMessageProcedure; Const Immediate: Boolean = False); Overload; Deprecated;
...
End;
The TMEMessageEnvelope is a wrapper for messages, defined as:
Type
TMEMessageEnvelope = Class(TMEPersistent)
Public
...
Property Infos: TMEMessageInfos Read FInfos;
Property Sender: TObject Read FSender;
Property Msg: TMessage Read FMsg;
End;
Receivers that subscribe via an envelope receiver will receive both synchronous and asynchronous messages. This is the preferred subscription method. Receivers that subscribe via an object method or via a procedure will only receive synchronous messages. This is maintained for compatibility with the RTL messaging system, but is deprecated.
The problem is that RTL messages cannot be posted, as they are. The subscribed consumers just provide a procedure or an object method to consume the message, right away. To post the message so as it can be consumed later, asynchronously, it needs to be wrapped and enqueued. This way the sender is isolated from the receivers. So actually... messages are posted (immediately or delayed) by first wrapping them in envelopes.
Here are the base interfaces envolved in this messaging system:
Type
IMEClonableMessage = Interface(IInterface)
['{45B223E2-DCA8-4E42-9847-6A3FCC910891}']
Function Clone: TMessage;
End;
IMEMessageSender = Interface(IInterface)
['{99AFDC4A-CE30-41A3-9AA5-D49F2F1106BD}']
Procedure PostDelayedMessage(const M: TMessage; Const DelayMSec: Cardinal);
Procedure PostMessage(Const M: TMessage);
Procedure SendMessage(Const M: TMessage);
End;
IMEEnvelopeSender = Interface(IInterface)
['{C3AEC52C-A558-40AB-B07B-3000ECDB9C0C}']
Procedure PostDelayedEnvelope(Const Envelope: TMEMessageEnvelope; Const DelayMSec: Cardinal);
Procedure PostEnvelope(Const Envelope: TMEMessageEnvelope);
Procedure SendEnvelope(Const Envelope: TMEMessageEnvelope);
End;
IMEEnvelopeReceiver = Interface(IInterface)
['{7D464713-2F25-4666-AAF8-757AF07688C3}']
Procedure ClearEnvelopes;
Procedure ProcessEnvelope;
Procedure ProcessEnvelopes;
Function QueueEnvelope(Const Envelope: TMEMessageEnvelope): Integer;
Procedure ReceiveEnvelope(Const Envelope: TMEMessageEnvelope);
Procedure Subscribe(Const AMessageClass: TClass);
Procedure Unsubscribe(Const AMessageClass: TClass);
End;
The IMEClonableMessage interface is used to clone messages... asynchronous messages must be cloned... because if there are many subscribers to the same message, each will receive and consume the message in different times, so it is best that each has its own copy of the message.
The other interfaces are, I think, self explanatory.
And finally here is the TMEMessageHandler class:
TMEMessageHandler = Class(TMEPersistent, IMEMessageSender, IMEEnvelopeSender, IMEEnvelopeReceiver)
/// <summary>Basic thread-safe class that can send and receive synchronous and asynchronous messages and envelopes.</summary>
Private
FLock: TObject;
FMessageManager: TMEMessageManager;
FSubscriptions: TDictionary<TClass, Integer>;
FEnvelopes: TObjectList<TMEMessageEnvelope>;
FOnReceiveEnvelope: TReceiveEnvelopeEvent;
FAutoProcessEnvelopes: Boolean;
Procedure _Lock;
Procedure _Unlock;
Procedure ClearSubscriptions;
Function GetMessageManager: TMEMessageManager;
Procedure SetAutoProcessEnvelopes(Const Value: Boolean);
Procedure SetMessageManager(Const Value: TMEMessageManager);
Protected
Function QueryInterface(Const IID: TGuid; Out Obj): HResult; Stdcall;
Function _AddRef: Integer; Stdcall;
Function _Release: Integer; Stdcall;
Procedure DoReceiveEnvelope(Const Envelope: TMEMessageEnvelope);
Procedure PostDelayedEnvelope(Const Envelope: TMEMessageEnvelope; Const DelayMSec: Cardinal);
Procedure PostDelayedMessage(Const M: TMessage; Const DelayMSec: Cardinal);
Procedure PostEnvelope(Const Envelope: TMEMessageEnvelope);
Procedure PostMessage(Const M: TMessage);
Procedure SendEnvelope(Const Envelope: TMEMessageEnvelope);
Procedure SendMessage(Const M: TMessage);
Function QueueEnvelope(Const Envelope: TMEMessageEnvelope): Integer;
Procedure ReceiveEnvelope(Const Envelope: TMEMessageEnvelope);
Public
Constructor Create; Override;
Destructor Destroy; Override;
Procedure ClearEnvelopes;
Procedure ProcessEnvelope;
Procedure ProcessEnvelopes;
Procedure Subscribe(Const AMessageClass: TClass);
Procedure Unsubscribe(Const AMessageClass: TClass);
Property AutoProcessEnvelopes: Boolean Read FAutoProcessEnvelopes Write SetAutoProcessEnvelopes Default True;
Property MessageManager: TMEMessageManager Read GetMessageManager Write SetMessageManager;
Property OnReceiveEnvelope: TReceiveEnvelopeEvent Read FOnReceiveEnvelope Write FOnReceiveEnvelope;
End;
How all this works
The TMEMessageHandler immediately delegates to the MessageManager any Subscribe and Unsubscribe calls. It will always subscribe providing itself as an IMEEnvelopeReceiver. It keeps track of subscriptions in its internal dictionary so as to be more efficient when it unsubscribes.
It also immediately delegates any call to the Send, Post and PostDelayed methods. The TMEMessageManager:
Sends messages to subscribed procedures (as RTL)
Sends messages to subscribed object methods (as RTL)
Sends envelopes to subscribed receivers by calling their
ReceiveEnvelope method
Posts envelopes (and envelope wrapped messages) to subscribed
receivers by calling their QeueEnvelope method with a cloned copy of
the envelope
Posts delayed envelopes (and envelope wrapped messages) to subscribed
receivers by enqueing them first in an internal lightweight thread
(TMEDelayedEnvelopeDeliverer) which has the message manager itself
deliver them when the delay has passed
As a receiver, the TMEMessageHandler implements the ReceiveEnvelope by simply delegating to the OnReceiveEnvelope event-handler.
Posted envelopes are received by the QueueEnvelope method, which adds the envelope in its thread-safe queue and then, but only if AutoProcessEnvelopes is True, uses the main thread's Queue to call its own ProcessEnvelope method (as by Remy's suggestion):
Function TMEMessageHandler.QueueEnvelope(Const Envelope: TMEMessageEnvelope): Integer;
Begin
_Lock;
Try
FEnvelopes.Add(Envelope);
Result := FEnvelopes.Count;
Finally
_Unlock;
End;
If AutoProcessEnvelopes Then
TThread.Queue(Nil,
Procedure
Begin
ProcessEnvelope;
End);
End;
The ProcessEnvelope method extracts the envelope from the thread-safe queue, calls the ReceiveEnvelope method (same as called by the message manager for synchronous messages) and then Frees the envelope (remember that this was a cloned copy just for this receiver):
Procedure TMEMessageHandler.ProcessEnvelope;
Var
E: TMEMessageEnvelope;
Begin
If FEnvelopes.Count > 0 Then Begin
_Lock;
Try
E := FEnvelopes.Extract(FEnvelopes[0]);
Finally
_Unlock;
End;
E.UpdateInfo(mieReceived);
ReceiveEnvelope(E);
E.Free;
End;
End;
The ProcessEnvelopes method just calls the former as many times as necessary to empty the asynchronous message queue:
Procedure TMEMessageHandler.ProcessEnvelopes;
Begin
While FEnvelopes.Count > 0 Do
ProcessEnvelope;
End;
How is the TMEMessageHandler used
Having defined TMELogMessage as an IMEClonableMessage to handle information to log, a minimal implementation for TMEFileLogger and other components looks like this:
Type
TMEFileLogger = Class(TMEComponent)
Private
...
FMessageHandler: TMEMessagehandler;
Protected
...
Procedure ReceiveLogEnvelope(Const Envelope: TMEMessageEnvelope);
Property MessageHandler: TMEMessagehandler Read FMessageHandler;
Public
Constructor Create(AOwner: TComponent); Override;
Destructor Destroy; Override;
...
End;
Constructor TMEFileLogger.Create(AOwner: TComponent);
Begin
Inherited;
...
FMessageHandler := TMEMessagehandler.Create;
MessageHandler.OnReceiveEnvelope := ReceiveLogEnvelope;
MessageHandler.Subscribe(TMELogMessage);
End;
Destructor TMEFileLogger.Destroy;
Begin
MessageHandler.Unsubscribe(TMELogMessage);
MessageHandler.ProcessEnvelopes;
FreeAndNil(FMessageHandler);
...
Inherited;
End;
Procedure TMEFileLogger.ReceiveLogEnvelope(Const Envelope: TMEMessageEnvelope);
Begin
If Envelope.Msg Is TMELogMessage Then
With Envelope.Msg As TMELogMessage Do
... something useful ...
End;
Sorry for the long post, but I hope this can be useful to someone else.
Queue implementations typically implement an event (OS synchronization object, not a VCL 'event') which application code can wait on. The event is set/fired/triggered/however you want to think of it whenever an item is added to an empty queue (or, if multiple items are added in a "batch", after they have all been added. The precise pattern may vary). If the queue in your case is your own implementation then I would consider adding such a mechanism to your implementation.
To avoid blocking the UI the application code creates a lightweight thread with the sole purpose of waiting on that queue event, de-queuing items from the queue into a UI thread-safe container and then notifying the UI thread that there are items to be processed. The monitoring thread then resumes waiting for the event to signal that there are yet more items in the queue.
In a VCL application the mechanism by which the monitoring thread notifies the UI could be a naive Synchronized procedure or (I would recommend) a message based notification posted to some form responsible for the UI processing of the items.
NOTE: The queue monitoring thread is also typically responsible for handling the case where the application/UI no longer cares about processing items (e.g. is shutting down) and so also listens for a "cancel" or "terminate" event which signals the thread to de-queue items but discard them (or deal with them in whatever way suits the application needs at this time) and then terminate (that is, the monitoring thread exits).
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.
I created a MDI Delphi app in Delphi XE2 that connects to a DataSnap server via a TSQLConnection component (driver = datasnap). A right-click on the TSQLConnection at design-time lets me generate the DataSnap client classes (ProxyMethods).
My goal is to have an elapsed time clock [0:00] on the client side that shows how long a DataSnap request takes to service, updated every 1 second. The two approaches that I have tried, but don't work are:
Method #1
Use a TTimer with a 1 second interval that updates the elapsed time clock while a ProxyMethod is being execute. I enable the timer just before calling the ProxyMethod. While the ProxyMethod is running, the OnTimer event doesn't fire -- a breakpoint in the code is never hit.
Method #2
Same as Method #1, except the timer is a TJvThreadTimer. While the ProxyMethod is running, the OnTimer event fires, but the OnTimer code doesn't get execute until after the ProxyMethod completes. This is evident because a breakpoint in the OnEvent code gets hit in rapid succession after the ProxyMethod completes -- like the OnTimer events have all been queued in the main VCL thread.
Furthermore, clicking anywhere on the client app while a slow ProxyMethod is running makes the app appear to be hung ("Not Responding" appears in title-bar).
I think the best solution is to move the execution of the ProxyMethods to a separate thread. However, there must be an existing solution -- because the related hung app issue seems like it would be a common complaint. I just can't find the solution.
Any suggestions are appreciated. Otherwise, I will resign myself to moving the ProxyMethod execution into a separate thread.
You have identified the fundamental problem. Your query is running in the UI thread and blocks that thread whilst it runs. No UI updates can occur, timer messages cannot fire etc.
I think the best solution is to move the execution of the ProxyMethods to a separate thread. However, there must be an existing solution -- because the related hung app issue seems like it would be a common complaint. I just can't find the solution.
You have already found the only solution to the problem. You must run your long-running query in a thread other than the UI thread.
In case anyone wants to know, the solution was rather simple to implement. We now have a working elapsed time clock [0:00] that increments anytime the client app is waiting for the DataSnap server to service a request. In essence, this is what we did. (A special thanks to those who share their solutions -- which helped guide my thinking.)
The server generated classes (ProxyMethods) must be created in the VCL thread, but executed in a separate thread. To do this, we created a ProxyMethods wrapper class and a ProxyMehtods thread class (all of which is contrived for this example, but still it illustrates the flow):
ProxyMethods.pas
...
type
TServerMethodsClient = class(TDSAdminClient)
private
FGetDataCommand: TDBXCommand;
public
...
function GetData(Param1: string; Param2: string): string;
...
end;
ProxyWrapper.pas
...
type
TServerMethodsWrapper = class(TServerMethodsClient)
private
FParam1: string;
FParam2: string;
FResult: string;
public
constructor Create; reintroduce;
procedure GetData(Param1: string; Param2: string);
procedure _Execute;
function GetResult: string;
end;
TServerMethodsThread = class(TThread)
private
FServerMethodsWrapper: TServerMethodsWrapper;
protected
procedure Execute; override;
public
constructor Create(ServerMethodsWrapper: TServerMethodsWrapper);
end;
implementation
constructor TServerMethodsWrapper.Create;
begin
inherited Create(ASQLServerConnection.DBXConnection, True);
end;
procedure TServerMethodsWrapper.GetData(Param1: string; Param2: string);
begin
FParam1 := Param1;
FParam2 := Param2;
end;
procedure TServerMethodsWrapper._Execute;
begin
FResult := inherited GetData(FParam1, FParam2);
end;
function TServerMethodsWrapper.GetResult: string;
begin
Result := FResult;
end;
constructor TServerMethodsThread.Create(ServerMethodsWrapper: TServerMethodsWrapper);
begin
FServerMethodsWrapper := ServerMethodsWrapper;
FreeOnTerminate := False;
inherited Create(False);
end;
procedure TServerMethodsThread.Execute;
begin
FServerMethodsWrapper._Execute;
end;
You can see that we split the execution of the ProxyMethod into two steps. The first step is to store the values of the parameters in private variables. This allows the _Execute() method to have everything it needs to know when it executes the actual ProxyMethods method, whose result is stored in FResult for later retrieval.
If the ProxyMethods class has multiple functions, you easily wrap each method and set an internal variable (e.g., FProcID) when the method is called to set the private variables. This way the _Execute() method could use FProcID to know which ProxyMethod to execute...
You may wonder why the Thread doesn't free itself. The reason is because I couldn't eliminate an error "Thread Error: The handle is invalid (6)" when the thread did its own cleanup.
The code that calls the wrapper class looks like this:
var
smw: TServerMethodsWrapper;
val: string;
begin
...
smw := TServerMethodsWrapper.Create;
try
smw.GetData('value1', 'value2');
// start timer here
with TServerMethodsThread.Create(smw) do
begin
WaitFor;
Free;
end;
// stop / reset timer here
val := smw.GetResult;
finally
FreeAndNil(smw);
end;
...
end;
The WaitFor suspends code execution until the ProxyMethods thread completes. This is necessary because smw.GetResult won't return the needed value until the thread is done executing. The key to making the elapsed time clock [0:00] increment while the proxy execution thread is busy is to use a TJvThreadTimer to update the UI. A TTimer doesn't work even with the ProxyMethod being executed in a separate thread because the VCL thread is waiting for the WaitFor, so the TTimer.OnTimer() doesn't execute until the WaitFor is done.
Informationally, the TJvTheadTimer.OnTimer() code looks like this, which updates the application's status bar:
var
sec: Integer;
begin
sec := DateUtils.SecondsBetween(Now, FBusyStart);
StatusBar1.Panels[0].Text := Format('%d:%.2d', [sec div 60, sec mod 60]);
StatusBar1.Repaint;
end;
Using the above idea, I made a simple solution that will work for all classes (automatically). I created TThreadCommand and TCommandThread as follows:
TThreadCommand = class(TDBXMorphicCommand)
public
procedure ExecuteUpdate; override;
procedure ExecuteUpdateAsync;
end;
TCommandThread = class(TThread)
FCommand: TDBXCommand;
protected
procedure Execute; override;
public
constructor Create(cmd: TDBXCommand);
end;
{ TThreadCommand }
procedure TThreadCommand.ExecuteUpdate;
begin
with TCommandThread.Create( Self ) do
try
WaitFor;
finally
Free;
end;
end;
procedure TThreadCommand.ExecuteUpdateAsync;
begin
inherited ExecuteUpdate;
end;
{ TCommandThread }
constructor TCommandThread.Create(cmd: TDBXCommand);
begin
inherited Create(True);
FreeOnTerminate := False;
FCommand := cmd;
Resume;
end;
procedure TCommandThread.Execute;
begin
TThreadCommand(FCommand).ExecuteUpdateAsync;
end;
And then changed Data.DBXCommon.pas:
function TDBXConnection.DerivedCreateCommand: TDBXCommand;
begin
//Result:= TDBXMorphicCommand.Create (FDBXContext, Self);
Result:= TThreadCommand.Create (FDBXContext, Self);
end;
Thanks of that, now I can do update of UI with server callback.
How did you force the compiler to use your modified
Data.DBXCommand.pas?
By putting modified Data.DBXCommand.pas in your project folder.
Trying to use the TThreadedQueue (Generics.Collections) in a single producer multiple consumer scheme. (Delphi-XE).
The idea is to push objects into a queue and let several worker threads draining the queue.
It does not work as expected, though.
When two or more worker threads are calling PopItem, access violations are thrown from the TThreadedQueue.
If the call to PopItem is serialized with a critical section, all is fine.
Surely the TThreadedQueue should be able to handle multiple consumers, so am I missing something or is this a pure bug in TThreadedQueue ?
Here is a simple example to produce the error.
program TestThreadedQueue;
{$APPTYPE CONSOLE}
uses
// FastMM4 in '..\..\..\FastMM4\FastMM4.pas',
Windows,
Messages,
Classes,
SysUtils,
SyncObjs,
Generics.Collections;
type TThreadTaskMsg =
class(TObject)
private
threadID : integer;
threadMsg : string;
public
Constructor Create( ID : integer; const msg : string);
end;
type TThreadReader =
class(TThread)
private
fPopQueue : TThreadedQueue<TObject>;
fSync : TCriticalSection;
fMsg : TThreadTaskMsg;
fException : Exception;
procedure DoSync;
procedure DoHandleException;
public
Constructor Create( popQueue : TThreadedQueue<TObject>;
sync : TCriticalSection);
procedure Execute; override;
end;
Constructor TThreadReader.Create( popQueue : TThreadedQueue<TObject>;
sync : TCriticalSection);
begin
fPopQueue:= popQueue;
fMsg:= nil;
fSync:= sync;
Self.FreeOnTerminate:= FALSE;
fException:= nil;
Inherited Create( FALSE);
end;
procedure TThreadReader.DoSync ;
begin
WriteLn(fMsg.threadMsg + ' ' + IntToStr(fMsg.threadId));
end;
procedure TThreadReader.DoHandleException;
begin
WriteLn('Exception ->' + fException.Message);
end;
procedure TThreadReader.Execute;
var signal : TWaitResult;
begin
NameThreadForDebugging('QueuePop worker');
while not Terminated do
begin
try
{- Calling PopItem can return empty without waittime !? Let other threads in by sleeping. }
Sleep(20);
{- Serializing calls to PopItem works }
if Assigned(fSync) then fSync.Enter;
try
signal:= fPopQueue.PopItem( TObject(fMsg));
finally
if Assigned(fSync) then fSync.Release;
end;
if (signal = wrSignaled) then
begin
try
if Assigned(fMsg) then
begin
fMsg.threadMsg:= '<Thread id :' +IntToStr( Self.threadId) + '>';
fMsg.Free; // We are just dumping the message in this test
//Synchronize( Self.DoSync);
//PostMessage( fParentForm.Handle,WM_TestQueue_Message,Cardinal(fMsg),0);
end;
except
on E:Exception do begin
end;
end;
end;
except
FException:= Exception(ExceptObject);
try
if not (FException is EAbort) then
begin
{Synchronize(} DoHandleException; //);
end;
finally
FException:= nil;
end;
end;
end;
end;
Constructor TThreadTaskMsg.Create( ID : Integer; Const msg : string);
begin
Inherited Create;
threadID:= ID;
threadMsg:= msg;
end;
var
fSync : TCriticalSection;
fThreadQueue : TThreadedQueue<TObject>;
fReaderArr : array[1..4] of TThreadReader;
i : integer;
begin
try
IsMultiThread:= TRUE;
fSync:= TCriticalSection.Create;
fThreadQueue:= TThreadedQueue<TObject>.Create(1024,1,100);
try
{- Calling without fSync throws exceptions when two or more threads calls PopItem
at the same time }
WriteLn('Creating worker threads ...');
for i:= 1 to 4 do fReaderArr[i]:= TThreadReader.Create( fThreadQueue,Nil);
{- Calling with fSync works ! }
//for i:= 1 to 4 do fReaderArr[i]:= TThreadReader.Create( fThreadQueue,fSync);
WriteLn('Init done. Pushing items ...');
for i:= 1 to 100 do fThreadQueue.PushItem( TThreadTaskMsg.Create( i,''));
ReadLn;
finally
for i:= 1 to 4 do fReaderArr[i].Free;
fThreadQueue.Free;
fSync.Free;
end;
except
on E: Exception do
begin
Writeln(E.ClassName, ': ', E.Message);
ReadLn;
end;
end;
end.
Update : The error in TMonitor that caused TThreadedQueue to crash is fixed in Delphi XE2.
Update 2 : The above test stressed the queue in the empty state. Darian Miller found that stressing the queue at full state, still could reproduce the error in XE2. The error once again is in the TMonitor. See his answer below for more information. And also a link to the QC101114.
Update 3 :
With Delphi-XE2 update 4 there was an announced fix for TMonitor that would cure the problems in TThreadedQueue. My tests so far are not able to reproduce any errors in TThreadedQueue anymore.
Tested single producer/multiple consumer threads when queue is empty and full.
Also tested multiple producers/multiple consumers. I varied the reader threads and writer threads from 1 to 100 without any glitch. But knowing the history, I dare others to break TMonitor.
Well, it's hard to be sure without a lot of testing, but it certainly looks like this is a bug, either in TThreadedQueue or in TMonitor. Either way it's in the RTL and not your code. You ought to file this as a QC report and use your example above as the "how to reproduce" code.
I recommend you to use OmniThreadLibrary http://www.thedelphigeek.com/search/label/OmniThreadLibrary when working with threads, parallelism, etc. Primoz made a very good job, and on the site you'll find a lot of useful documentation.
Your example seems to work fine under XE2, but if we fill your queue it fails with AV on a PushItem. (Tested under XE2 Update1)
To reproduce, just increase your task creation from 100 to 1100 (your queue depth was set at 1024)
for i:= 1 to 1100 do fThreadQueue.PushItem( TThreadTaskMsg.Create( i,''));
This dies for me every time on Windows 7. I initially tried a continual push to stress test it, and it failed at loop 30...then at loop 16...then at 65 so at different intervals but it consistently failed at some point.
iLoop := 0;
while iLoop < 1000 do
begin
Inc(iLoop);
WriteLn('Loop: ' + IntToStr(iLoop));
for i:= 1 to 100 do fThreadQueue.PushItem( TThreadTaskMsg.Create( i,''));
end;
I looked for the TThreadedQueue class but don't seem to have it in my D2009. I'm not exactly going to kill myself over this - Delphi thread support has always been err.. errm... 'non-optimal' and I suspect that TThreadedQueue is no different :)
Why use generics for P-C (Producer / Consumer) objects? A simple TObjectQueue descendant will do fine - been using this for decades - works fine with multiple producers/consumers:
unit MinimalSemaphorePCqueue;
{ Absolutely minimal P-C queue based on TobjectQueue and a semaphore.
The semaphore count reflects the queue count
'push' will always succeed unless memory runs out, then you're stuft anyway.
'pop' has a timeout parameter as well as the address of where any received
object is to be put.
'pop' returns immediately with 'true' if there is an object on the queue
available for it.
'pop' blocks the caller if the queue is empty and the timeout is not 0.
'pop' returns false if the timeout is exceeded before an object is available
from the queue.
'pop' returns true if an object is available from the queue before the timeout
is exceeded.
If multiple threads have called 'pop' and are blocked because the queue is
empty, a single 'push' will make only one of the waiting threads ready.
Methods to push/pop from the queue
A 'semaHandle' property that can be used in a 'waitForMultipleObjects' call.
When the handle is signaled, the 'peek' method will retrieve the queued object.
}
interface
uses
Windows, Messages, SysUtils, Classes,syncObjs,contnrs;
type
pObject=^Tobject;
TsemaphoreMailbox=class(TobjectQueue)
private
countSema:Thandle;
protected
access:TcriticalSection;
public
property semaHandle:Thandle read countSema;
constructor create; virtual;
procedure push(aObject:Tobject); virtual;
function pop(pResObject:pObject;timeout:DWORD):boolean; virtual;
function peek(pResObject:pObject):boolean; virtual;
destructor destroy; override;
end;
implementation
{ TsemaphoreMailbox }
constructor TsemaphoreMailbox.create;
begin
{$IFDEF D2009}
inherited Create;
{$ELSE}
inherited create;
{$ENDIF}
access:=TcriticalSection.create;
countSema:=createSemaphore(nil,0,maxInt,nil);
end;
destructor TsemaphoreMailbox.destroy;
begin
access.free;
closeHandle(countSema);
inherited;
end;
function TsemaphoreMailbox.pop(pResObject: pObject;
timeout: DWORD): boolean;
// dequeues an object, if one is available on the queue. If the queue is empty,
// the caller is blocked until either an object is pushed on or the timeout
// period expires
begin // wait for a unit from the semaphore
result:=(WAIT_OBJECT_0=waitForSingleObject(countSema,timeout));
if result then // if a unit was supplied before the timeout,
begin
access.acquire;
try
pResObject^:=inherited pop; // get an object from the queue
finally
access.release;
end;
end;
end;
procedure TsemaphoreMailbox.push(aObject: Tobject);
// pushes an object onto the queue. If threads are waiting in a 'pop' call,
// one of them is made ready.
begin
access.acquire;
try
inherited push(aObject); // shove the object onto the queue
finally
access.release;
end;
releaseSemaphore(countSema,1,nil); // release one unit to semaphore
end;
function TsemaphoreMailbox.peek(pResObject: pObject): boolean;
begin
access.acquire;
try
result:=(count>0);
if result then pResObject^:=inherited pop; // get an object from the queue
finally
access.release;
end;
end;
end.
I don't think TThreadedQueue is supposed to support multiple consumers. It's a FIFO, as per the help file. I am under the impression that there's one thread pushing and another one (just one!) popping.