In case someone wants to know, the solution was quite simple to implement. Now we have the working elapsed time hours [0:00] that increase anytime the client application expects the DataSnap server to serve the request. In essence, this is what we have done. (Special thanks to those who share their decisions, which helped me figure it out.)
Server-generated classes (ProxyMethods) must be created in the VCL thread, but run in a separate thread. To do this, we created the wrapper class ProxyMethods and the class of streams ProxyMehtods (all this is far-fetched for this example, but it still illustrates the stream):
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 divided the execution of ProxyMethod into two steps. The first step is to save the parameter values ββin private variables. This allows the _Execute() method to have everything that it needs to know when it executes the actual ProxyMethods method, the result of which is stored in FResult for later retrieval.
If the ProxyMethods class has several functions, you can easily transfer each method and set an internal variable (for example, FProcID ) when you call the method to set private variables. Thus, the _Execute() method could use FProcID to find out which ProxyMethod to execute ...
You may wonder why Thread does not free itself. The reason is that I could not resolve the error "Stream error: descriptor is invalid (6)" when the stream did its own cleanup.
The code calling the shell class is as follows:
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;
WaitFor pauses code execution until the ProxyMethods thread exits. This is necessary because smw.GetResult will not return the required value until the thread is executed. The key to increasing the elapsed time interval [0:00] while the proxy server thread is busy is to use TJvThreadTimer to update the user interface. A TTimer does not work even when running ProxyMethod in a separate thread, because the VCL thread is waiting for WaitFor , so TTimer.OnTimer() not executed until WaitFor is executed.
Informatively, the TJvTheadTimer.OnTimer() code looks like it updates the application 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;