Hi all - I need some advice on introducing async remote procedure calls in to ABL webclient GUIs ( or telling whether what I'm trying to achieve is actually advisable / possible with our current architecture ).
It's an OE 11.7 webclient which re-uses a single session managed connection to a stateless classic Appserver. Every RPC from any client window is done on a handle to the same connection.
I am able to make an ASYNC call ON LEAVE of a mobile phone field, set a flag with the reponse and later validate this flag on OK press. It looks something like what the documentation suggests:
However I'm having a few issues and I don't think I fully understand what's happening...
Apologies for the extended question but any advice much appreciated :)
1. The example that you site shows the syntax of the operation. But you should never write code this way. You already have a WAIT-FOR in effect for the GUI. You do NOT want another one. You should do the asynchronous RUN and just return back to the UI. When the response comes in, the one WAIT-FOR you already have will detect it and process it by running your PROCEDURE-COMPLETE event (which obviously has to be in scope, i.e., in a persistent procedure). I think this doc should be fixed to explain that in a real application you would not have a WAIT-FOR statement there.
I think that will take care of your first bullet. There will be no WAIT-FOR when you do the async RUN, so it won't interrupt anything.
2. I believe your 2nd & 3rd bullets are correct. You will have to look to others for advice on that.
3. You should not get any error or STOP condition because you've run more than 1 async request. If the server is busy, they will get queued up. If you can say specifically what error message you are getting, I might be able to be more helpful.
P.S. I am going to log a doc bug, based on my previous comment.
To avoid the wait-for, as mentioned in the docs, I used a temp-table. I had a situation where I needed to do some calculation for a lot of products. I used an async stateless appserver for this, along this lines (more or less pseudo-code)
DEFINE TEMP-TABLE ttTodo NO-UNDO FIELD cItemNr AS CHARACTER FIELD hRequest AS HANDLE. /* Create a to-do list of all items */ FOR EACH bItem NO-LOCK: CREATE bTodo. ASSIGN bTodo.cItemNr = bItem.itemNr. END. CREATE SERVER hServer. hServer:CONNECT("-URL slater:OuterLimits64@zeus:8810/.../apsv"). /* Start all processes */ FOR EACH bTodo: RUN getItemData.p ON hServer ASYNCHRONOUS SET ttTodo.hRequest EVENT-PROCEDURE "RequestComplete" IN THIS-PROCEDURE (INPUT ttTodo.cItemNr, OUTPUT TABLE ttItemData APPEND). END. /* Wait for all processes to complete */ DO WHILE TEMP-TABLE ttTodo:HAS-RECORDS: FOR EACH bTodo: IF bTodo.hRequest:COMPLETE THEN DELETE bTodo.
END.
PROCESS EVENTS.
END.
hServer:DISCONNECT().
PROCEDURE RequestComplete:
DEFINE INPUT PARAMETER TABLE FOR ttItemData.
/* do some interesting stuff */
END PROCEDURE.
Ahh thanks [mention:04fbfb2e92784123a464ff2aade602b1:e9ed411860ed4f2ba0265705b8793d05] that WAIT-FOR in the documentation example completely threw me. As you say it's a little misleading - ideally there could be a separate example specific to not locking up a UI (since this is a common use case for Async).
I've removed the WAIT-FOR and can now make multiple async requests, with the EVENT-PROCEDUREs completing in time. I now just need to either tidy up after myself or do something persistent as I'm getting 8982 errors if I close the window when there are outstanding async requests.
Ideally I want to create an isolated resuable piece of code that any window can access in order to:
- create a new connection for async requests ( if there isn't one already for the window )
- call remote procedues from various GUI triggers and store the reponse in a global variable / property
- access the variable / property e.g. on OK press was the mobile phone validation
- process events & disconnect when closing a screen to prevent any errors
I will update the thread with my progress but any other advice much appreciated :)
Thanks [mention:e666fffb14004b29b4bad87b731999a8:e9ed411860ed4f2ba0265705b8793d05] I think I can see the benefit of storing the async-requests for the window in a single TT rather than separate objects.
If I understand it right it's an elegant way of keeping track and destroying the request objects, after the responses are definitely in?
Hi Patrick,
I found this thread while looking for a simular setup. I also found the wait-for suggestion in the documentation, while a long time ago someone in our team created a repeat + process events loop.
What would be the reason not use the wait-for solution? I found the solution below (with and without process events) working fine. I'm not a big fan of a continuous repeat myself.
But on the other hand, thats probably happening in the background while using a wait-for.
do i = 1 to 3: log-manager:WRITE-MESSAGE ("open requests? " + string(hServer:ASYNC-REQUEST-COUNT)). //process events. WAIT-FOR PROCEDURE-COMPLETE OF hRequest[i]. DELETE OBJECT hRequest[i] NO-ERROR. end.
Glad you brought this up because I was already thinking of responding to the PROCESS EVENTS example. PROCESS EVENTS should follow the same rules as a WAIT-FOR. They are both what we refer to as IO-Blocking statements.
As I said, if you are running in a GUI app, you already have a WAIT-FOR. You don’t need another one, Yes, it will usually work. But historically you couldn’t use a WAIT-FOR everywhere (e.g, not in functions), so people got into trouble. That restriction has almost entirely gone away, but there is still one case left if you are using .NET for your UI. And if you’re not doing that...? Why not use the prescribed model? .Net has the same model and enforces it. We don’t enforce it. It is up to the developer to do the right thing. Remember, if you have multiple WAIT-FORs you need to ensure that they are terminated in the correct order. Otherwise you will get a Stop condition. So I believe it is less bug prone and cleaner to do it the right way. You just need to get used to the idea.
And if you are doing this on an AppServer, you’d have to be running async requests on another server. That is the only time you should do this as there is no other WAIT-FOR. And presumably you are doing this to parcel out requests to different sessions simultaneously. Otherwise there’s no point in using async. And if you have this model, WAIT-FOR is much more efficient than PROCESS EVENTS.
Thanks Laura
That ties in with the "one WAIT-FOR rule" referred to in various forums e.g.
knowledgebase.progress.com/.../P12116
So to clear things up...
For me with my regular GUI example (not .NET) I can't WAIT-FOR after each request in my trigger blocks as it annoyingly delays the trigger APPLY and could lead to STOP errors. However since the request objects are scoped to my window procedure I need to process them all before window close to prevent 8982 errors on reponse. Are you saying these days I can use either WAIT-FOR or PROCESS-EVENTS to safely do this? Or is there some way of letting the existing GUI WAIT-FOR them before it lets the window close?
For Patrick / Rutger on the Appserver it seems that using the WAIT-FOR between each request would effectively be the same as running each request synchronously so as you say fairly pointless. But you're saying after the requests have been sent they're better off using WAIT-FOR if required as it's more efficient than PROCESS-EVENTS and totally safe in backend code?
Your problem is that the PROCEDURE-COMPLETE event handlers are scoped to the “window procedure”. I assume you mean the procedure where your extra WAIT-FOR currently is. You need to put it into a persistent procedure. When the event fires, we make the async request handle available (as SELF I believe). Once you’ve processed all requests, you can delete the persistent procedure. I.e., it can delete itself inside the handler. Then you don’t need to block with a WAIT-FOR to keep everything from going out of scope. You can just return back to your existing WAIT-FOR. Try it out.
Correct on your 2nd paragraph.