An asynchronous function launcher for CSP

Introduction
When an action takes much time, the browser from where you launched the action (with a #server call for example) is like frozen. Nothing can be done by the user, and the screen is not refreshed anymore. At the end, the session times out (specially if it's short) and the user is in the no man's land of the application...

The best is then to trigger the actions from the browser and in Caché to launch a job that will do its task in the background and report when it's finished. Meanwhile the user is free to navigate with the browser, and he can eventually return to the page periodically to see if it's finished, as the progression can be indicated.

Here is my implementation of this problem.

Principles
The trick is to allow a job to communicate with the rest of the application.

This is achieved by storing data in ^CacheTempAsyncThread global. To enable multiple jobs at the same time, the first node is the job name (argument sender in the functions below). Child nodes contain the following values:
 * progressTotal : the maximum value reached by the progression, enabling to render a progression percentage
 * progressValue : the current progress value
 * progressTitle : the job title, so that we can list all jobs name and state in a grid
 * state : the current state, as discussed below
 * error : the last error returned by the job

A status is assigned to the job and stored in the state node. It has the values:
 * run : the job is launched and performing its task
 * abort : this state is put by the sender. Should the running method handles it, the process would terminates.
 * done : the job is finished (the thread has been disposed by Caché)
 * error : the job had an error and has been terminated. the node error contains the error description
 * none : This state allows the sender to know that the job is finished and the results were collected, thus stopping to track the job.

The job has the task of running a classmethod. This classmethod has no argument and only return the error description. The arguments and results can be stored by the caller before launching the task and by the thread, in a dedicated storage within ^CacheTempAsyncThread (see function Variables in the code to store/recall data).

To indicate the progress of the task the running classmethod periodically calls ProgressStep, that increments the progress value and notifies the abort request.

Part 1 : The routine
//Routine: asyncthread.mac //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //* * * * * * Global Asynchronous function execution * * * * * * * //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // //Empty vars, results and progress Init(sender) k ^CacheTempAsyncThread(sender) q //The laucher: creates a new job and init the progress values NewThread(sender,title,command,progressTotal) //initialize progression bar and state s ^CacheTempAsyncThread(sender,"progressTotal")=progressTotal s ^CacheTempAsyncThread(sender,"progressValue")=0 s ^CacheTempAsyncThread(sender,"progressTitle")=title s ^CacheTempAsyncThread(sender,"state")="run" s ^CacheTempAsyncThread(sender,"error")="" //launch job (wait 1 second before timeout) job ThreadRun(sender,command)::1 //the job failed to start i '$TEST q "jobError" //job launched, return to caller without error q "" //The Job itself //Arguments: //		sender: string to identify the caller //		command: name of the package _ name of the class. method e.g. "MyPack.MyClass_MyMethod" ThreadRun(sender,command) s $ZT="TrapRun" //link sender to job s ^CacheTempAsyncThread(sender)=$JOB //link job to sender for progression s ^CacheTempAsyncThread("jobs","sender",$JOB)=sender //run class method, that returns an empty string if success or an error string s ^CacheTempAsyncThread(sender,"error")=$zobjclassmethod($P(command,"_",1),$P(command,"_",2)) //indicates the job was aborted by the caller i ^CacheTempAsyncThread(sender,"state")="abort" s ^CacheTempAsyncThread(sender,"error")="aborted" i ^CacheTempAsyncThread(sender,"error")'="" s ^CacheTempAsyncThread(sender,"state")="error" //indicates the job is finished successfully s ^CacheTempAsyncThread(sender,"state")="done" //no error, forces 100% completion i ^CacheTempAsyncThread(sender,"error")="" s ^CacheTempAsyncThread(sender,"progressValue")=^CacheTempAsy _ ncThread(sender,"progressTotal") //no need anymore k ^CacheTempAsyncThread("jobs","sender",$JOB) q TrapRun ; //an error occured: method unknown or trapped s ^CacheTempAsyncThread(sender,"error")="trap" s ^CacheTempAsyncThread(sender,"state")="error" k ^CacheTempAsyncThread("jobs","sender",$JOB) q //This function allows the sender to abort a thread (if implemented in the job method) ThreadAbort(sender) s ^CacheTempAsyncThread(sender,"state")="abort" //Get the variables global name inside a thread //use it as: 	s var=$$Variables(mySender) //		w @var@(myVarName) //	or	s @var@(myVarName)=myValue Variables(sender="") q:sender="" "^CacheTempAsyncThread("_^CacheTempAsyncThread("jobs","sender",$JOB)_",""vars"")" q "^CacheTempAsyncThread("""_sender_""",""vars"")"

//Perform one step of progression and return job abortion request //Threads call this function as: s abort=$$asyncProgressStep^conv ProgressStep s ^CacheTempAsyncThread($G(^CacheTempAsyncThread("jobs","sender",$JOB),"term"),"progressValue")=$I(^CacheTempAsyn _ cThread($G(^CacheTempAsyncThread("jobs","sender",$JOB),"term"),"progressValue")) i $G(^CacheTempAsyncThread($G(^CacheTempAsyncThread("jobs","sender",$JOB),"term"),"state"))="abort" q 1 q 0

//Returns the current state of the thread State(sender) q $G(^CacheTempAsyncThread(sender,"state"),"none")

//Returns the last error Error(sender) q $G(^CacheTempAsyncThread(sender,"error"),"")

//Returns the title of the job Title(sender) q $G(^CacheTempAsyncThread(sender,"progressTitle"),"") //Return the current progress value of a thread Progress(sender) s prog=$G(^CacheTempAsyncThread(sender,"progressValue"),0)*100\$G(^CacheTempAsyncThread(sender,"progressTotal"),1) q $S(prog<0:0,prog>100:100,1:prog) //Indicates the process cycle is finished ThreadFinished(sender) s ^CacheTempAsyncThread(sender,"state")="none"