How to Use Instructions That Have Asynchronous Behavior
What is asynchronous behavior?
Asynchronous behavior is when the execution of an instruction starts a process that runs independently of the SCADA Basic program that called it. The calling SCADA Basic program execution continues without waiting for the result of the independent process. Most of the instructions that delegate part or all of the processing to another module behave in this way to avoid unacceptable delay in the calling program execution or it being blocked (waiting for the other processing to be completed). For example,, instructions related to:
- Access to historical data where waiting for the data would result in an unacceptable delay,
- Handling of communication objects (start, stop...) where waiting for the end to end execution can take a long time depending on your configuration (time-out), driver and network performances,
- Script instructions requiring processing on another PcVue station on the network, having a producer station (server) doing the actual task when the calling program is executing on a consumer station (client) for instance.
The syntax of all these instructions includes an argument that is the name of a variable. The real-time value of the variable indicates the progress status of the independent process. For some instructions, the variable is mandatory and a change in variable value is used to initiate further action on an event when the independent process is complete. For other instructions the variable is optional and may be used to initiate further action, or as an indication to the user.
Example of instructions and modes that have asynchronous behavior
| Instruction | Modes | Status variable |
| EXPORT | GENERATE, GENERATE_DATES AND GENERATE_PERIOD | Optional |
| EXPORT_LOG | GETRECORD and GETSTATISTIC | Mandatory |
| EXPORT_TREND | GETRAW, GETSAMPLED, GETSTATISTIC and GETAGGREGATED | Mandatory |
| FILETRANSFER | DOWNLOAD, DOWNLOAD_DIRECTORY and DELETE | Optional |
| HISTORY | GETTREND | Optional |
| MAPDISPLAY | LOADMARKERS | Optional |
| SELECTOR | HISTORICAL | Optional |
| SVBATCH | SELECT | Optional |
| SVLOG | EXTRACT | Optional |
| SVTREND | GETTREND | Optional |
The status variable is optional in the syntax although should be considered mandatory in all but the most trivial of uses.
As of version 12 and the introduction of scope 'session' for variables, events and cyclic functions, it is strongly advise to use session variables for variable ResultVar when calling an asynchronous instruction. The goal is to avoid the risk of concurrency races if the same (asynchronous) script can be executed at the same time in multiple user sessions. Designing asynchronous scripting by taking advantage of variables, events and cyclic with scope 'session' allows for isolating asynchronous script execution as part of a user session, therefore avoiding the complexity of handling concurrency, or the risk triggering an event in all user sessions in cases only one user session is involved.
How to use the status variable to initiate processing of the results
The program must monitor the change in value of the status variable in order to take informed decisions depending on the execution progress and success or failure to complete of the asynchronous process.
The accepted method to use the status variable is to programmatically add an event that is triggered by the change in value of the status variable. The event then runs a program that processes the result of the asynchronous instruction. The event is added before execution of the asynchronous instruction and deleted after the result of the asynchronous instruction has been processed. Its structure might look something like this.
'Content of the script file MyProgram
Sub Initialize ()
'-------- Add event
EVENT("ADDPROG", "StatusVarName", "TriggerCondition", "MyProgram", "", "ProcessResult");
'-------- Asynchronous instruction
INSTRUCTION1("MODE", "P1", "P2",...., "StatusVarname");
End Sub
Sub ProcessResult ()
'-------- Delete event
EVENT("DELPROG", "StatusVarName", "TriggerCondition", "MyProgram", "", "ProcessResult");
'-------- Process result of asynchronous instruction
INSTRUCTION1;
INSTRUCTION2;
.....
.....
End Sub
Using the DELAY instruction is not a solution with regard to the asynchronous nature of such instructions. It must not be used as a replacement to correctly handling status variables because:
-
It blocks the execution of the caller script for the duration of the delay,
-
There is no guarantee that the asynchronous operation is completed within the delay.
Example
'-------- Simplified Export_Log example
Sub Export_Log_EX1()
Const BIT_TO_0 = 8192, BIT_TO_1 = 16384;
Dim dTimenow as Double, dEvents As Double;
'-------- Initialise variables
dTimeNow = DateTimeValue();
dEvents = BIT_TO_0 + BIT_TO_1;
'-------- Set up event
Event("ADDPROG", "SVB.ExportLogStatus", "S>S", "E.svb", "", "OnChangeOfStatus");
'-------- Start Export_Log
Export_Log("GETRECORD", "Loglist02", dTimeNow - 600000, dTimeNow, dEvents, 0, 29, "", "@SVB.ExportLogStatus", ";", ",", 0, "#D/#M/#Y|#h:#m:#s|#E|#T", "Date|Time|Event|Title");
End Sub
'-------- On change of status variable process if complete else clean up
Sub OnChangeOfStatus()
Dim sStatus As Single;
Dim lBufferHandle As Long;
Dim iLines As Integer;
sStatus = SVB.ExportLogStatus;
If(sStatus==0 || sStatus==4 || sStatus ==5) Then ' Export complete or reached limits
Print("Export_Log complete - status is ", sStatus);
lBufferHandle = Alloc_Buffer(102400);
iLines = Export_Log("READBUFFER", lBufferHandle);
If (iLines > 0) Then
BufToExcel(lBufferHandle, ";", ",", "export.xlsx", "ex1", "APPEND", iLines);
Print("Exported ",iLines, " lines to Excel");
End If
Export_Log("DISPOSE");
Free_Buffer(lBufferHandle);
Event("DELPROG", "SVB.ExportLogStatus", "S>S", "E.svb", "", "OnChangeOfStatus");
Else
If(sStatus==1) Then ' Export running - do nothing
Print("Export_Log running");
Else ' Export failed - cancel and clean up
Print("Export_Log failed - status is ", sStatus);
Export_Log("CANCEL");
Export_Log("DISPOSE");
Event("DELPROG", "SVB.ExportLogStatus", "S>S", "E.svb", "", "OnChangeOfStatus");
End If
End If
End Sub