Skip to main content

Using Tasklocal Variables

Tasklocal variables are cycle-consistent. In a task cycle, they are written only by a defined task, while all other tasks have read-only access. It is taken into account that tasks can be interrupted by other tasks or can run simultaneously. The cycle consistency also applies above all if the application is running on a system with a multicore processor.

Therefore, using task local global variable lists is one way to automatically achieve a synchronization (by the compiler) when multiple tasks are processing the same variables. This is not the case when using ordinary GVLs. Multiple tasks can write simultaneously to ordinary GVL variables during a cycle.

However, it is imperative to note: The synchronization of tasklocal variables requires a relatively large amount of time and memory and is not always the best solution for every application. For this reason, see below for more detailed technical information and "best practice" guidance to help you make the right decision.

In the CODESYS project, the _cds_icon_gvl_tasklocal.png Global Variable List (tasklocal) object is available for defining tasklocal variables. Syntactically, it corresponds to a normal GVL, but also contains the information of the task that has write access to the variables. Then all variables in such a GVL are not changed by another task during a cycle of a task.

Tip

Tasklocal variables are complex variables whose value cannot be changed in online mode using the Write Values command.

The next section contains a simple example that demonstrates the principle and functionality of tasklocal variables. It includes a writing program and a reading program. The programs run in different tasks, but they access the same data that is stored in a tasklocal global variable list so that they are processed cycle-consistently.

Showing functionality in an example

See below for Instructions on reprogramming this sample application.

Example 14. Sample application
(* task-local GVL, object name: "Tasklocals" *)
VAR_GLOBAL
        g_diaData : ARRAY [0..99] OF DINT;
END_VAR

PROGRAM ReadData
VAR
        diIndex : DINT;
        bTest : BOOL;
        diValue : DINT;
END_VAR
bTest := TRUE;
diValue := TaskLocals.g_diaData[0];
FOR diIndex := 0 TO 99 DO
    bTest := bTest AND (diValue = Tasklocals.g_diaData[diIndex]);
END_FOR

PROGRAM WriteData
VAR
        diIndex : DINT;
        diCounter : DINT;
END_VAR
diCounter := diCounter + 1;
FOR diIndex := 0 TO 99 DO
        Tasklocals.g_diaData[diIndex] := diCounter;
END_FOR


The programs WriteData and ReadData are called by different tasks.

In the program WriteData, the array g_diaData is populated with values. The program ReadData tests whether or not the values of the array are as expected. If so, then the variable bTest yields the result TRUE.

The array data that is tested is declared via the variable g_diaData in the object Tasklocals of type Global Variable List (tasklocal). This synchronizes the data access in the compiler and guarantees cycle consistency, even when the accessing programs are called from different tasks. In the sample program, this means that the variable test is always TRUE in the program ReadData.

If the variable g_diaData were declared only as a global variable list in this example, then the test (the variable test in the program ReadData) would yield FALSE more often. In this case, this is because one of the two tasks in the FOR loop could be interrupted by the other task, or both tasks could run simultaneously (multicore controllers). And therefore the values could be changed by the writer while the reader reads the list.

Constraints in the declaration

Important

An online change of the application is not possible after changes in declarations in the list of tasklocal variables.

Note the following when declaring a global tasklocal variable list:

  • Do not assign direct addresses by means of an AT declaration.

  • Do not map to tasklocal variables in the controller configuration.

  • Do not declare any pointers.

  • Do not declare any references.

  • Do not instantiate any function blocks.

  • Do not declare any tasklocal variables as PERSISTENT and RETAIN at the same time.

The compiler reports write access in a task without write access as an error. However, not all write-access violations can be detected. The compiler can only assign static calls to a task. However, the call of a function block by means of a pointer or an interface is not assigned to a task, for example. As a result, any write access is not recorded there either. Moreover, pointers can point to tasklocal variables. Therefore, data can be manipulated in a read task. In this case, a runtime error is not issued. However, values that are modified by means of pointer access are not copied back in the shared reference of variables.

Properties of tasklocal global variables and possible behavior

The variables are located at a different address in the list for each task. For read access, this means: ADR(variable name) yields a different address in each task.

The synchronization mechanism guarantees the following:

  • Cycle consistency

  • Freedom from locked states: A task never waits for an action from another task at any time.

With this method, however, no time can be determined when a reading task securely receives a copy of the writing task. Fundamentally, the copies can deviate. In the example above, it cannot be concluded that each written copy is processed one time by the reader. For example, the reading task can edit the same array over multiple cycles, or the contents of the array can skip one or more values between two cycles. Both can occur and have to be considered.

The writing task can be paused for one cycle between two accesses to the shared reference by each reading task. This means that when n reading tasks exist, the writing task can have n cycles of delay until the next update of the shared reference.

In each task, the writing task can prevent a reading task from getting a reading copy. As a result, no maximum number of cycles can be specified after which a reading task will definitely receive a copy.

In particular, this can become problematic if very slow running tasks are involved. Assuming a task runs only every hour and cannot access the tasklocal variables during this time, then the task works with a very old copy of the list. Therefore, it can be useful to insert a timestamp in the tasklocal variables so that the reading tasks can at least determine whether or not the list is up-to-date. You can set a timestamp as follows: Add a variable of type LTIME to the list of tasklocal variables and add the following code to the writing task, for example: tasklocal.g_timestamp := LTIME();.

Best practice

Tasklocal variables are designed for the "Single writer – multiple readers" use case. When you implement a code that is called by different tasks, using tasklocal variables is a significant advantage. For example, this is the case for the sample application appTasklocal as described above when it is extended by multiple reading tasks that all access the same array and use the same functions.

Tasklocal variables are especially useful on multicore systems. On these systems, you cannot synchronize tasks by priority. Then other synchronization mechanisms become necessary.

Do not use tasklocal variables when a reading task always has to work on the newest copy of the variable. Tasklocal variables are not suitable for this purpose.

A similar issue is the "Producer–Consumer" dilemma. This happens when a task produces data and another task processes the data. Choose another type of synchronization for this configuration. For example, the producer could use a flag to notify that a new date exists. Then the consumer can use a second flag to notify that it has processed its data and is waiting for new input. In this way, both can work on the same data. This removes the overhead for cyclic copying of data, and the consumer does not lose any data generated by the producer.

Monitoring

At runtime, multiple different copies of the tasklocal variable list may exist in memory. When monitoring a position, not all values can be displayed. Therefore, the values from the shared reference are displayed for inline monitoring, in the watch list, and in the visualization for a tasklocal variable.

When you set a breakpoint, the data of the task is displayed that ran to the breakpoint and was halted as a result. Meanwhile, the other tasks continue running. Under certain circumstances, the shared copy can be changed. In the context of the halted task, however, the values remain unchanged and are displayed as they are. You need to be aware of this.

Background: Technical implementation

For a list of tasklocal variables, the compiler creates a copy for each task, as well as a shared reference copy for all tasks. This creates a structure that contains the same variables as the list of tasklocal variables. Moreover, an array with this structure is created in which an array dimension is created for each task. As a result, an array element is indexed for each task. If a variable in the list is accessed now in the code, then the tasklocal copy of the list is actually accessed. Furthermore, it is determined in which task the block is currently running and the access is indexed accordingly.

For example, the line of code diValue := TaskLocals.g_diaData[0]; from the above example is replaced by:

diValue := __TaskLocalVarsArray[__CURRENTTASK.TaskIndex].__g_diarr[0];

The __CURRENTTASK operator is available in CODESYS V3.5 SP13 and higher in order to quickly determine the current task index.

At runtime, at the end of the writing task, the contents of the tasklocal list are written to the global list. For a reading task at the beginning, the contents of the shared reference are copied to the tasklocal copy. Therefore, for n tasks, there are n+1 copies of the list: One list serves as a shared reference and every task also has its own copy of the list.

A scheduler controls the time-based execution of multiple tasks and therefore also task switching. The strategy, which is tracked by the scheduler in order to control the allocation of the execution time, has the goal of preventing a task from being blocked. The synchronization mechanism is therefore optimized to the properties of tasklocal variables to prevent blocking states (lock states) and at no time does a task wait for the action of another task.

Synchronization strategy:

  • As long as the writing task writes a copy back to the shared reference, none of the reading tasks gets a copy.

  • As long as a reading task gets a copy of the common reference, the writing task does not write back a copy.

Instructions for creating the sample application as described above

Aim: With a program ReadData, you want to access the same data that is written by a program WriteData. Both programs should run in different tasks. You make the data available in a tasklocal variable list so that it is processed automatically in a cycle-consistent manner.

Requirement: A brand new standard project is created and open in the editor.

  1. Rename the application from Application to appTasklocal.

  2. Below appTasklocal, add a program in ST named ReadData.

  3. Below appTasklocal, add another program in ST named WriteData.

  4. Below the object Task Configuration, rename the default task from MainTask to Read.

  5. In the Configuration dialog of the task Read, click the Add Call button to call the program ReadData.

  6. Below the Task Configuration object, add another task named Write, and add the call of the program Write to this task.

    Now there are two tasks Write and Read in the task configuration which call the programs WriteData and ReadData, respectively.

  7. Select the application appTasklocal and add an object of type Global Variable List (taskLocal).

    The Add Global Variable List (tasklocal) dialog opens.

  8. Specify the name as Tasklocals.

  9. Select the Write task from the Task with write access list box.

    _cds_img_tasklocal_objekcts.png

    The object structure for using tasklocal variables within an application is complete. Now you can code the objects as described in the example above.