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;