Asynchronous Job Execution¶
The critical factor in many applications is the cycle time of a task. The smaller this can be, the more frequently the signals of the processes can be checked, which are to be controlled by the program steps of this task. This minimizes the application’s response time for changes in process signals. In particular the Cycle time may be then as short as possible, if the number of instructions processed in one cycle corresponds exactly to the number of instructions strictly necessary to meet the given requirements. If a particular job does not have to be completed in one cycle, and the transfer of this job to another task with a longer cycle time is not an option, the individual calculation steps are assigned to different groups of operations (states) and the execution of each group (state) is moved to one of the following cycles. This means that only the computing time for one statement group is required in the current cycle. This approach reaches its limit when a statement group needs an unpredictable period of time for its calculation, or under certain circumstances can even block the complete program execution. In this case, such a group of statements must be attached to a background task so that the cycle time of the foreground task is not affected. A transport mechanism is required so that the parameters, states, and the calculation results can be exchanged consistently between the foreground and the background task.
Applications in CODESYS¶
Distribution of Work Load over more the one Cycle¶
The CODESYS Common Behaviour Model provides a set of function blocks based on the CBML.IActionProvider
interface.
The typical structure of the CyclicAction
method for distributing the work load over more the one cycle my be look like this:
METHOD CyclicAction
VAR_INPUT
itfTimingController : CBML.ITimingController;
END_VAR
VAR_OUTPUT
xComplete : BOOL;
iErrorID : INT;
END_VAR
REPEAT
// working to reach the ready condition
// ⇒ xComplete := TRUE
// if the maximum invocation time is reached
// ⇒ xTimeLimit := TRUE
// if the maximum operating time is reached
// ⇒ xTimeOut := TRUE
// if an error condition is reached
// ⇒ set iErrorID to a value other than 0 (Zero)
itfTimingController.ControllerCheckTiming(
xTimeOut=>xTimeOut,
xTimeLimit=>xTimeLimit
);
xComplete := TRUE;
iErrorID := ERROR.NO_ERROR;
UNTIL xComplete OR
xTimeOut OR xTimeLimit OR
iErrorID <> ERROR.NO_ERROR
END_REPEAT
The CyclicAction
is running until either xComplete
is TRUE
or iErrorID
≠ 0 (Zero).
This holds the fact that as long as xComplete
equals FALSE
and eErrorID
is zero, the method is called
in every cycle and can do a little bit of work on a bigger task with every call.
With itfTimingController
≠ 0 (Zero) it is possible to check the current invocation
time (see: ITimingController.ControllerCheckTiming
). Function blocks with a udiTimeLimit
input variable
can then be implement in such a way that the current invocation is exited when the consumed time for this invocation
has exceeded the settings from udiTimeLimit
.
This is the recommended procedure if the statements used in relation to the calling program do not have a blocking effect.
CmpAsyncMgr¶
The component CmpAsyncMgr
of the CODESYS runtime system provides the infrastructure for implementing asynchronous
job execution. It is possible to define the name and the priority of the background task, enqueue a reference to a
method of an function block instance together with its actual parameter set and a specific job state report variable.
The attached CODESYS project
illustrates how to handle the different
functions and data structures.
From a library developers point of view, the current state of the implementation of the CmpAsyncMgr
has some drawbacks.
The asynchronous executed method is not executed in the context of an IEC task.
It is not possible to remove a pending job from the job queue.
Every background task has its own job queue. It is not possible to link more the one task to one job queue
It is not possible to assign a background task to a specific task group. So the multi core support is limited.
AsyncJobManager¶
To solve these issues the new AsyncJobManager.library
was specified. The provided infrastructure is completely
independent of the CmpAsyncMgr
component. The functionality of the CmpIecTask
component was utilised to realize
a background task with an IEC context (IBackGroundTask
).
FUNCTION_BLOCK BackGroundTask EXTENDS CBML.LConC IMPLEMENTS IBackGroundTask, FBF.IInstance
VAR_INPUT CONSTANT
tgTaskGroup : TASK_GROUP;
anAppName : APP_NAME;
tnTaskName : TASK_NAME;
usiTaskPrio : USINT;
udiTaskInterval : UDINT;
xWatchdogEnabled : BOOL;
udiWatchdogTime : UDINT;
usiWatchdogSensitivity : USINT;
END_VAR
VAR_INPUT
itfParams : SHD.ISharedQueue;
itfAction : IAsyncActionProvider;
END_VAR
VAR_OUTPUT
eErrorID : ERROR;
itfResults : SHD.ISharedArea;
dwCurrentCycleTime : DWORD;
dwAverageCycleTime : DWORD;
dwMaxCycleTime : DWORD;
dwMinCycleTime : DWORD;
dwCycleCounter : DWORD;
END_VAR
INTERFACE IBackGroundTask EXTENDS __SYSTEM.IQueryInterface
METHOD TaskDisableScheduling : BOOL
METHOD TaskEnableScheduling : BOOL
METHOD TaskDisableWatchdog : BOOL
METHOD TaskEnableWatchdog : BOOL
METHOD TaskResetStatistics : BOOL
Separated from the implementation of a function block for abstracting a background task, a additional interface
specification for actions (IAsyncActionProvider
) was made.
The action is modeled as a method of a function block instance (AsyncAction
)
and will be cyclically called until the return value xComplete
indicating a condition that the asynchronous operation
is now completed and the method needs no further calling. The input parameter itfParam
is a generic pointer to a data
structure containing the current parameter values (SHD.IQueueableNode
).
Attention
The implementation of an IAsyncActionProvider
must ensure that the OnlineChange
is not blocked!
If the IAsyncActionProvider
-Instance is to block to fulfill specific requirements,
then the events PrepareOnlineChange
and OnlineChangeDone
must be used
to ensure that the IAsyncActionProvider
-Instance runs without blocking for the duration of the OnlineChange
.
INTERFACE IAsyncActionProvider EXTENDS __SYSTEM.IQueryInterface
METHOD AsyncAction
VAR_INPUT
itfParam : SHD.IQueueableNode;
END_VAR
VAR_OUTPUT
xComplete : BOOL;
END_VAR
PROPERTY GET AsyncResult : SHD.ISharedArea
The following use cases where in mind while specifying this library.
Simple One-To-One Relationship¶
One action (
IAsyncActionProvider
) is assigned to one background task.The current foreground task fills the parameter queue with parameter sets.
The result of the background task is available via the
AsyncResult
property.

Some Background Tasks are sharing Parameters and Results¶
Every instance of a
BackgroundTask
function block is connected to one and the same instance of anIAsyncActionProvider
.One parameter queue is connected to the group of
BackgroundTask
s. Each task will fetch one of the next parameter sets to its context for executing theIAsyncActionProvider.AsyncAction
with this set of parameters.The results of these joint efforts are available for the foreground task through the common
SHD.ISharedArea
.

Several Background Tasks Are Connected In A Row¶
Like on a conveyor belt, the BackgroundTask
s are connected in series via their parameter/result queues.
Each task processes a portion of a chain of transformations and passes its result on to its subsequent task.

The Parameter Queue feeding the Background Task¶
The “SharedData Utilities” library provides the interface SHD.ISharedQueue
.
INTERFACE ISharedQueue EXTENDS __SYSTEM.IQueryInterface
METHOD Dequeue : IQueueableNode
VAR_OUTPUT
/// Insertion point in time
ltTimeStamp : LTIME;
eErrorID : ERROR;
END_VAR
METHOD Enqueue : ERROR
VAR_INPUT
itfNode : IQueueableNode;
END_VAR
A instance of the BackgroundTask
function block will get out the parameters (via Enqueue
method) for its IAsyncActionProvider
instance.
One possible implementation can be the SHD.SharedQueue
which is defined in the “SharedData Utilities” library.
Example: Connecting Parameters
to a BackgroundTask
function block
VAR
sqParameters : SHD.SharedQueue;
itfActionProvider : AJM.IAsyncActionProvider (* := myActionProvider *);
bgtBackgroundTask : AJM.BackgroundTask := (
tgTaskGroup:='IEC-Tasks',
anAppName:='Application',
tnTaskName:='BackgroundTask',
usiTaskPrio:=10,
udiTaskInterval:=50000,
itfParams := sqParameters,
itfAction := itfActionProvider
);
END_VAR
To feed the BackgroundTask with new parameters the call of the method ISharedQueue.Enqueue
is necessary.
The related paramter structure is behind the itfNode reference.
INTERFACE IQueueableNode EXTENDS __SYSTEM.IQueryInterface
METHOD NodeDispose
PROPERTY GET IsNodeValid : BOOL
Behind the IQueueableNode
any proper implementation is possible.
Thus the parameter structure is freely customizable and can be very well adapted to special requirements.
FUNCTION_BLOCK Parameter IMPLEMENTS SHD.IQueueableNode
VAR_INPUT
(* Any required data structure *);
END_VAR
The method NodeDispose
and the property IsNodeValid
are utilized to handle resource management and provide the
possibility to mark a parameter set as not valid any more while it is staying in the queue.
The Background Task’s Result¶
The SHD.ISharedArea
interface is defined in the “SharedData Utilities” library.
The implementation behind provides a consistent transport of data structures for example between multiple
cores of a processor.
INTERFACE ISharedArea EXTENDS __SYSTEM.IQueryInterface
METHOD AreaSetObserver : ISharedAreaObserver
VAR_INPUT
itfAreaObserver : ISharedAreaObserver;
END_VAR
VAR_OUTPUT
eErrorID : ERROR;
END_VAR
One possible implementation of SHD.ISharedAreaObserver
can be the SHD.SharedQueue
which
is defined in the “SharedData Utilities” library.
Example: Handling the results of a BackgroundTask
function block
VAR
sqResults : SHD.SharedQueue;
xObserved : BOOL;
itfNode : SHD.IQueueableNode;
eErrorID : SHD.ERROR;
bgtBackgroundTask : AJM.BackgroundTask;
END_VAR
bgtBackgroundTask();
IF bgtBackgroundTask.xBusy THEN
IF NOT xObserved THEN
bgtBackgroundTask.itfResult.AreaSetObserver(sqResult);
xObserved := TRUE;
END_IF
itfNode := sqResult.Dequeue(eErrorID=>eErrorID);
__QUERYINTERFACE(itfNode, itfSharedAreaRef);
IF itfSharedAreaRef <> 0 THEN
(* Process the results *)
itfNode.NodeDispose();
itfNode := 0;
itfSharedAreaRef := 0;
END_IF
END_IF
Note
With CODESYS V3.5 SP13 a new operator is available.
__CurrentTask
It gives access to a structure containing two members:
TaskIndex
andpTaskInfo
.
TaskIndex
is a zerobased index identifying the task, pTaskInfo
can be assigned to a POINTER TO Task_Info2
from the CmpIecTask
library to get detailed information about the executing task.
Using the IecTaskGetCurrent
function was until today the usual way to get information about the currently running task.