How to Use Instructions That Have Asynchronous Behavior

Concept Link IconApplies to

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