As part of my "re factoring" project of converting 4GL windows UI to .net UI, I wanted to ask the brains in here a question: If the program is running through some large amount of records, we throw some information onto the screen to show progress. FOR EACH LotsOfRecordsInThisTable NO-LOCK lv_i = 1 TO lv_i + 1: IF lv_i MOD 500 EQ 0 THEN DO: ASSIGN FRAME :TITLE = STRING(lv_i). PROCESS EVENTS. END. END. Now, how do you push feedback to the UI when it is separated ? By a method / procedure call / message ? FOR EACH LotsOfRecordsInThisTable NO-LOCK lv_i = 1 TO lv_i + 1: IF lv_i MOD 500 EQ 0 THEN MyUIClass:ProgressMessage(lv_i). END.
As part of my "re factoring" project of converting 4GL windows UI to .net UI, I wanted to ask the brains in here a question:
If the program is running through some large amount of records, we throw some information onto the screen to show progress.
FOR EACH LotsOfRecordsInThisTable NO-LOCK lv_i = 1 TO lv_i + 1:
IF lv_i MOD 500 EQ 0 THEN
ASSIGN FRAME :TITLE = STRING(lv_i).
Now, how do you push feedback to the UI when it is separated ? By a method / procedure call / message ?
FOR EACH LotsOfRecordsInThisTable NO-LOCK lv_i = 1 TO lv_i + 1:
IF lv_i MOD 500 EQ 0 THEN MyUIClass:ProgressMessage(lv_i).
if that were the case, I would reason that the code would check if it is running on appserver and either create a "dummy" UI (a class that just has empty methods) or not call the UI at all
Not every one uses appserver, by the way !
That would be one way.
I guess the standard way to do this is to publish an event of some sort. The event listener is then responsible for doing something with the event. In this case you'd break the decision of when (the mod 500) and the what (display/process events) into the listener. This gives you the opportunity to not actually have the listener attached in which case nothing gets displayed. You have lots of choices for the event itself such as publish/subscribe or write a listener using classes.
For client/server code the event should work fine. If the processing happens on the AppServer it's far more complicated since the AppServer can't communicate back to the client (using just plan ABL methods).
I've solved this in the past by breaking the big piece of work on the AppServer into chunks. This is the only situation where I've allowed myself to use persistent procedures over the appserver boundary to maintain the state of the process within that procedure on the appserver.
Basically the client runs the .p persistent on hAppserver. This initializes the process. Then the client runs, process_chunk where the appserver processes the first 500 rows in the query and then get's back to the client with the information that there is additional work and a status (500 rows processed or even a percentage if it's possible to compute that).
The client then calls into process_chunk in the procedure handle on the appserver again to continue the work.
The easiest way to maintain the position in the query to continue the work is by using a QUERY instead of a FOR EACH.
While the client is displaying the status information, you could also cancel the further processing. Have a cancel button somewhere and a trigger on that button that sets a logical variable lCancel to true. Just add a PROCESS EVENTS before/after the status display and check lChancel after the PROCESS EVENTS.
I'm waiting desperately for this client to upgrade to 10.1C, to do the termination of the persistent procedure in a safe place like the FINALLY block.
yeah, the thing is you wouldn't want the "listener" to be called every record, as that would be inefficient.
Mike, could not you run async on the appserver, and have the appserver process update some status field as it is running. The client could then make periodic checks on that status field.
That's another option we use as well from time to time.
I try to avoid the asnc request as the "one size fits all" option. The client needs to appservers (or even more ) appserver. I worker process (bound to the asnc routine for as long as the process goes) and the one server that's used for answering the requests to answer the GUI reuqests (like normal data updates, the status queries, etc.).
When we run jobs that require us to read their status from a database table, I prefer to use a scheduling routine (batch process) rather than asyn appserver processes.
To my knowledge, we asked PSC about that a few years ago, Appserver cannot be called asynchronously and it does not seem to have been changes on that. We finally built the "chunk" solution.
Could somebody (from PSC) confirm ?
It definively CAN. Check for the ASYNCHRONOUS (or similar) option of the RUN statement.
In a GUI client you need a second connection. One for the async - long running - process and a second one for the remaining GUI work.
The only event you get in return back, is when the RUN statement finishes (a procedure can be defined to receive the OUTPUT parameters of the AppServer as INPUT parameters). So no out of the box solution for status responses.
Coming late to this party ... being on the left coast and across the pond from such active participants results in quite a morning backlog ... it seems like we need to clarify the question a bit. While the discussion moved on to various AppServer issues and batching, I'm not sure we have the context clear.
In the original post, it sounded like it wasn't AppServer at all, but a client accessing the database. In such a context, there is a limited potential for creating layers since everything is running in one session. One can create some sense of layers by isolating each layer's activity in separate objects, but the operation is likely to be more intimate than one would want in a truly layered application. Among other issues, the session is single threaded, so the DB activity can't really happen independently of the UI activity.
If one introduces AppServer or other messaging, then the actual DB activity and UI activity are occurring in separate sessions. In that case, one has two different types of problem. One is characterized by the DB activity resulting directly in UI, e.g., populating a browser. The solution there, as has been mentioned, is probably batching, but with batching the need for the signal is gone since the arrival of a batch is its own signal. The other problem type is where the task involves a lot of DB activity and no UI until the end, e.g., printing a report or doing some large calculation. In this case there is a need for a signal to indicate progress. This is an ideal situation for a bus since the DB process can simply send periodic signals and the UI session can pick them up and indicate progress without being locked into that task.
right on both counts. Something we are interested in trying is to see if we can use a non-visual .net component in an appserver session - if so, we can easily connect up to a jabber server and send appropriate messages.
I'll let you know ..
Why not go Sonic and do it right ... ?
because a large portion (a "C" open source package) of my infrastructure doesn't "talk" sonic. It just talks http or jabber
a) I don't want to spend the time and investment modifiying the code to talk to sonic
b) Jabber is free, easy to setup, administer and use
c) I don't want to have to learn how to setup, administer and use sonic ..
I'm not saying sonic is not worth it - just not in my particular case.
Well, depends on what you are talking about ... if all you ever use messaging for is to send progress messages for long running actions and you therefore don't need reliable messaging, then Sonic would be overkill. But, if you are serious about that architectural modernization and a move to a layered architecture with service orientation, then there are lots of things that are ideally implemented with a reliable messaging system. Getting on the bus may not be worth while for this particular need, but there is a lot of value in getting there and this would provide a simple task for getting started.