Products

Price/Order

Support

Partners

Testimonials

Test Results

About us

Contact
 Design Suggestions
Bottom
 
Total posts: 8
 Author Design Suggestions
Gary Bell

18.09.2006 14:23:57
Registered user
Danijel,

I'm making progress in learning the ins and outs of RTC.  I absolutely love the flexibility you provide in passing parameters/results in remote functions.   I have a couple of specialized servers I've written using Indy that I'm thinking about converting over to RTC.  Both run as a service and basically accept ASCII commands along with parameters and then do some action and send data back, much like an FTP server would.

Programming was pretty easy with the blocking style of Indy and I'm comfortable with that.   With RTC, the server side of things is very easy as well, since everything is pretty much received asynchronously. It fits well in the event driven model.  However, I'm trying to get my thoughts on the best way to handle the client in the event driven model.

My client piece runs a series of tasks that need to be run in a certain order and need to ensure it completes successfully before I proceed to the next step.  Here's one idea.  Basically have an event that I reset before calling the remote function.  I then do a WaitFor on that event, which will be signaled in the OnReturn event for the function I just called.   In my WaitFor logic, I set a 50ms timeout value. If I timeout I process messages to keep things running and then return to my Event.WaitFor.  I exit out of that loop only if the event is signaled, errors out or abandoned.

Here's a snippet that shows my basic concept.


 for i:=0 to Tasks.Count-1 do
  begin
    ResultDone.ResetEvent;
    with RtcClientModule1 do
    begin
      with Data.NewFunction(Tasks[i].FunctionName) do
      begin
        asString['SomeParameter'] := Tasks[i].Params[i];
        Call(RtcResult1);
      end;
    end;
    repeat
      WRes := ResultDone.WaitFor(50);
      case WRes of
        wr_Abandoned,wr_Error : Break;
        wr_Timeout:
          begin
           Application.ProcessMessages;
          end;
      end;
    until WRes in [wr_Abandoned,wr_Error,wr_Signaled];
  end;

procedure TForm1.RtcResult1Return(Sender: TRtcConnection; Data,
  Result: TRtcValue);
var
  i : Integer;
  S : String;
begin
  // do something with the result
  ResultDone.SetEvent;
end;


I suppose another way to do this would be to keep a state machine counter and then in the OnReturn event increment the counter and post a message to some procedure that calls that particular task.

Anyway, I was wondering what your thoughts are on this type of programming issue.

Thanks for any input,

Gary Bell
Danijel Tkalcec [RTC]

18.09.2006 14:58:26
Registered user
Hi Gary,

although your approach will also work, you are going into too much trouble with that. There is already a mechanism integrated in RTC Clients which handles waiting for a result to return and an automated mechanism which will ensure a request is reposted a sepcified number of times if it doesn't succeed the first time. All requests are sent out in exactly the order you post them.

The only thing you need, if you want to wait for a result from a remote function call before proceeding, is to use the WaitForCompletion method, which is implemented for TRtcHttpClient and TRtcClientModule components. Using that method, you can either wait infinitely (until you receive all results or all results have been aborted), or wait a specified ammount of time.

As said ... RTC will make sure your requests are sent in the exact order you have posted them (using the "Call" or "Post" method) and by setting the AutoRepost property, you can specify how many times you want a request to be re-posted (re-sent) in case a communication error should occure.

I would recommend setting AutoRepost to 2 and using infinite timeout with the WaitForCompletion method. If there will be a problem in communication (connecting, sending or receiving data) and it couldn't be resolved after your specified number retries, you will receive ResultAborted events and the WaitForCompletion method will return FALSE (set AutoRepost to 0 for no retries; -1 for unlimited retries; any other value for a specific number of retries, if necessary).

Best Regards,
Danijel Tkalcec
Gary Bell

18.09.2006 17:16:40
Registered user
Danijel,

Thanks, I should have know better.  And, with your explanation I think I have better grasp on this.  Can you verify the following logic?   


  for i:=0 to Tasks.Count-1 do
  begin
    with RtcClientModule1 do
    begin
      with Data.NewFunction(Tasks[i].SomeFunctionName) do
      begin
        asString['SomeParameter'] := Tasks[i].SomeParameter;
        Call(RtcResult1);
      end;
    end;
  end;
  RtcClientModule1.WaitForCompletion;
  //==========================
  // OnReturn Code
  //==========================
   if Result.asRecord.asInteger['CompletionCode'] = 0 then
   begin
   //  DoSomething with the contents of the record...
   end
   else
     TRtcDataClient(Sender).SkipRequests;


In my tests,  this seemed to work.  My main concern was that even though I queued up a bunch of remote function calls, that one would not actually be sent to the server until the previous one had been executed and a result had been returned, at which time I could abort the whole thing if necessary depending on the results I received.

Thanks again,

Gary
Danijel Tkalcec [RTC]

18.09.2006 18:08:26
Registered user
I've never considered calling "SkipRequests" from the OnResult event of a remote function call to cancel the remaining requests, but now that I see it, I think it's an interesting approach. In any case, it will work. Using WaitForCompletion to wait for completion on multiple requests is also a good idea. :)

Btw ... when you expect complex data types (asRecord, asArray, asDataSet or asByteStream), you should validate that data you have received is really of the type you are expecting. IOW, before accessing the field "asRecord.asInteger[]", you should check that the Result you have received is really of the expected type (rtc_Record), like this:
if Result.isType=rtc_Record then
  if Result.asRecord.asInteger['CompletionCode'] =0 then
The same goes for receiving complex data types as parameters sent to a remote function call and ... for receiving values contained in other complex data types. By validating the type of data you received, you can handle unexpected situations graciously, without generating access violations (for example, asRecord would return NIL if there was value assigned and you would get an AV trying to use asRecord.asInteger['...']).

Best Regards,
Danijel Tkalcec
Gary Bell

18.09.2006 21:42:15
Registered user
Thanks, actually I did have that validation in there, but didn't include it in the snippet.

I had run across the SkipRequests in the help file and tried it.   As you said, it does seem to work.  Is there a better way I should be handling that situation?

Thanks - Gary
Danijel Tkalcec [RTC]

18.09.2006 21:49:54
Registered user
If you only need to be able to skip remaining calls (waiting in your request queue), using SkipRequests is a good approach, which will also work when MultiThreaded and HyperThreading are set to False.

Btw ... if you should need to call this-or-that remote function depending on a result you get from some other remote function, you can also set RtcHttpClient.MultiThreading=True and RtcClientModule.HyperThreading=True, then you can call remote functions from within the OnResult/ResultAborted events (when you receive a result from the last function call).

Best Regards,
Danijel Tkalcec
Danijel Tkalcec [RTC]

19.09.2006 16:04:54
Registered user
I just got one more idea what you could do, in case you want to send N remote calls to the Server and your Server can decide if it wants to continue processing the requests in the "package" or abort processing.

What you can do is use "StartCalls;" before the "Call()" loop, then use "Post" (after the loop) to send all remote function calls to the Server in a single HTTP request. On the Server, your remote functions will be executed one-by-one, until all have been processed or one of them raises an exception. The rest of the functions received inside the same request will be skipped and your client will receive results from all functions which have been completed, plus the exception message with the last function that was executed (which raised the exception).

Using that method, you don't need to call "SkipRequests", since all requests would be sent out in a package and there would be nothing to skip. You would still receive all results as if you were sending each call in a separate request (like you are doing now - without StartCalls/Post).

I'm not sure if your client or your Server makes the decision if the other functions "in a list" should be processed, but if your Server is the one making the decision, it would probably be "cleaner" to send all function calls inside a "package" (StartCalls/Post) and raise an exception on the Server (inside OnExecute event of the function which has to break execution of all other functions from the same request).
 //==========================
  // Client-side preparing the calls
  //==========================
  RtcClientModule1.StartCalls;
  for i:=0 to Tasks.Count-1 do
  begin
    with RtcClientModule1 do
    begin
      with Data.NewFunction(Tasks[i].SomeFunctionName) do
      begin
        asString['SomeParameter'] := Tasks[i].SomeParameter;
        Call(RtcResult1);
      end;
    end;
  end;
  // Post all prepared calls in a single request ...
  RtcClientModule1.Post;
  RtcClientModule1.WaitForCompletion;

  //==========================
  // Client-side OnReturn Code
  //==========================
   begin
   if Result.isType=rtc_Exception then
     begin
     // Exception was raised on the Server
     // Error message (string) is in Result.asException
     end
   else
     begin
     // process the result
     end;
  end;

  //==========================
  // Server-side OnExecute Code
  //==========================
  begin
  if Something_is_Not_Right then
    raise Exception.Create('That is not right! Abort!');
  // Do what was planned ...
  end;

Best Regards,
Danijel Tkalcec
Gary Bell

19.09.2006 21:05:35
Registered user
As always.  Thanks!