Tasklokale Variablen sind zykluskonsistent. Sie werden in einem Taskzyklus nur von einer definierten Task geschrieben, während alle anderen Tasks nur lesend darauf zugreifen können. Berücksichtigt wird dabei, dass Tasks durch andere Tasks unterbrochen werden können oder gleichzeitig laufen können. Die Zykluskonsistenz gilt vor allem auch, wenn die Applikation auf einem System mit Mehrkernprozessor läuft.
Das Verwenden tasklokaler globaler Variablenlisten ist somit eine Möglichkeit, automatisch eine Synchronisation zu erhalten (durch den Compiler), wenn mehrere Tasks dieselben Variablen bearbeiten. Dies ist bei der Verwendung normaler GVLs nicht der Fall. Auf normale GVL-Variablen können während eines Zyklus gleichzeitig mehrere Tasks schreiben.
Beachten Sie allerdings unbedingt: Die Synchronisierung tasklokaler Variablen ist relativ zeit- und speicheraufwändig und nicht in jedem Anwendungsfall das geeignete Mittel. Sehen Sie deshalb zur Entscheidungsfindung weiter unten detailliertere technische Informationen und Hinweise zur "Best Practice".
Im CODESYS-Projekt steht das Objekt „Globale Variablenliste (tasklokal)“ für das Definieren tasklokaler Variablen zur Verfügung. Syntaktisch entspricht es
einer normalen GVL, enthält aber zusätzlich die Angabe der Task, die die Schreibrechte
auf die Variablen hat. Alle Variablen in einer solchen GVL werden dann während eines
Zyklus einer Task nicht durch eine andere Task verändert.
Im nächsten Abschnitt finden Sie ein einfaches Beispiel, das das Prinzip und die Funktionalität tasklokaler Variablen zeigt: Es gibt ein schreibendes und ein lesendes Programm. Die Programme laufen in verschiedenen Tasks, greifen aber auf dieselben Daten zu, die in einer tasklokalen globalen Variablenliste liegen, damit sie zykluskonsistent bearbeitet werden.
Funktionalität in einem Beispiel dargestellt
Eine Anleitung zum Nachprogrammieren dieser Beispielapplikation finden Sie weiter unten.
Beispielapplikation
(* 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 diCounter := 0 TO 99 DO Tasklocals.g_diaData[diIndex] := diCounter; END_FOR
Die Programme „WriteData“ und „ReadData“ werden von verschiedenen Tasks aufgerufen.
Im Programm WriteData
wird das Array g_diaData
mit Werten gefüllt. Das Programm ReadData
testet, ob die Werte des Arrays so sind wie erwartet. Wenn dies der Fall ist, liefert
die Variable bTest
als Ergebnis TRUE
.
Die Arraydaten, die getestet werden, sind über die Variable g_diaData
im Objekt Tasklocals
vom Typ Globale Variablenliste (tasklokal)
deklariert. Dadurch werden die Datenzugriffe im Compiler synchronisiert und die
Daten sind garantiert zykluskonsistent, auch wenn die zugreifenden Programme aus
verschiedenen Tasks heraus aufgerufen werden. Im Beispielprogramm heißt das konkret,
dass die Variable test
im Programm ReadData
immer TRUE
ist.
Wenn die Variable g_diaData
in diesem Beispiel nur als globale Variablenliste deklariert wäre, würde der Test
, also die Variable test
im Programm ReadData
, häufiger FALSE
liefern. Denn in diesem Fall könnte eine der beiden Tasks in der FOR
-Schleife durch die andere Task unterbrochen werden, oder es könnten beide Tasks gleichzeitig
laufen (Multicore-Steuerungen). Und somit könnten die Werte vom Schreiber verändert
werden, während der Leser die Liste ausliest.
Einschränkungen bei der Deklaration




HINWEIS

Nach Änderungen in Deklarationen in der Liste der tasklokalen Variablen bearbeiten ist kein Online-Change der Applikation möglich.
Beachten Sie Folgendes beim Deklarieren einer gobalen tasklokalen Variablenliste:
-
Weisen Sie keine direkten Adressen über eine AT-Deklaration zu.
-
Mappen Sie in der Steuerungskonfiguration nicht auf tasklokale Variablen.
-
Deklarieren Sie keine Pointer.
-
Deklarieren Sie keine Referenzen.
-
Instanziieren Sie keine Funktionsbausteine.
-
Deklarieren Sie tasklokale Variablen nicht gleichzeitig als
PERSISTENT
undRETAIN
.
Ein schreibender Zugriff in einer Task ohne Schreibrecht wird vom Compiler als Fehler gemeldet. Allerdings können nicht alle Stellen ermittelt werden, an denen gegen das Schreibrecht verstoßen wird. Der Compiler kann nämlich nur statische Aufrufe einer Task zuordnen. Der Aufruf eines Funktionsbausteins über einen Pointer oder ein Interface wird aber beispielsweise nicht einer Task zugeordnet. Somit werden dort eventuelle Schreibzugriffe auch nicht erfasst. Außerdem können Pointer auf tasklokale Variablen zeigen. So können in einer lesenden Task Daten manipuliert werden. In diesem Fall wird ebenfalls kein Laufzeitfehler ausgegeben. Allerdings werden bei Zugriff über Pointer geänderte Werte nicht in die gemeinsame Referenz der Variablen zurückkopiert.
Eigenschaften der tasklokalen globalen Variablen und mögliches Verhalten
Die Variablen liegen in der Liste für jede Task auf einer anderen Adresse.
Das heißt für Lesezugriffe: ADR(variable name)
liefert in jeder Task eine andere Adresse.
Der Synchronisationsmechanismus garantiert Folgendes:
-
Zykluskonsistenz
-
Freiheit von Lockzuständen: Zu keinem Zeitpunkt wartet eine Task auf eine Aktion einer anderen Task.
Mit dieser Methode kann allerdings kein Zeitpunkt bestimmt werden, zu dem eine lesende Task eine Kopie der schreibenden Task sicher erhält. Grundsätzlich können die Kopien auseinanderlaufen. Im Beispiel oben kann man nicht davon ausgehen, dass jede geschriebene Kopie einmal vom Leser bearbeitet wird. Die lesende Task kann beispielsweise mehrere Zyklen lang das gleiche Array bearbeiten, oder der Inhalt des Arrays kann zwischen zwei Zyklen ein oder mehrere Werte „überspringen“. Beides kann vorkommen und muss berücksichtigt werden.
Die Schreibtask kann zwischen zwei Zugriffen auf die gemeinsame Referenz durch jede
lesende Task für einen Zyklus aufgehalten werden. Das heisst, wenn n
lesende Tasks existieren, kann die Schreibtask n
Zyklen Verzug haben bis zur nächsten Aktualisierung der gemeinsamen Referenz.
Eine lesende Task kann in jedem Zyklus durch die schreibende Task davon abgehalten werden, eine Lesekopie zu bekommen. Man kann daher keine maximale Zahl an Zyklen angeben, nach der eine lesende Task sicher eine Kopie erhält.
Insbesondere kann dies problematisch werden, wenn sehr langsam laufende Tasks beteiligt
sind. Angenommen eine Task läuft nur jede Stunde und kann dann nicht auf die tasklokalen
Variablen zugreifen, dann arbeitet die Task mit einer sehr alten Kopie der Liste.
Deshalb kann es sinnvoll sein, einen Zeitstempel in die tasklokalen Variablen einzufügen,
über den die lesenden Tasks zumindest feststellen können, ob die Liste aktuell ist.
Einen Zeitstempel können Sie folgendermaßen anbringen: Fügen in die Liste der tasklokalen
Variablen eine Variable vom Typ LTIME
ein und in die schreibende Task beispielsweise den folgenden Code:
tasklocal.g_timestamp := LTIME();
.
Best Practice
Tasklokale Variablen sind für den Anwendungsfall „Einzelner Schreiber - mehrere Leser"
ausgelegt. Wenn Sie einen Code implementieren, der von verschiedenen Tasks aufgerufen
wird, ist die Verwendung tasklokaler Variablen von großem Vorteil. Beispielsweise
ist das bei der oben beschriebenen Beispielapplikation appTasklocal
der Fall, wenn diese um mehrere Lesetasks erweitert ist, die alle auf das gleiche
Array zugreifen und die gleichen Funktionen verwenden.
Tasklokale Variablen sind insbesondere auf Systemen mit Mehrkernprozessor hilfreich. Auf diesen Systemen können Sie Tasks nicht über die Priorität synchronisieren. Dann entsteht die Notwendigkeit für andere Synchronisierungsmechanismen.
Verwenden Sie keine tasklokale Variablen, wenn eine lesende Task immer auf der neuesten Kopie der Variablen arbeiten muss. Dafür sind tasklokale Variablen nicht geeignet.
Ein ähnliches Problem ist die „Producer - Consumer“-Problematik. Diese ist gegeben, wenn eine Task Daten produziert und eine zweite Task diese verarbeitet. Bevorzugen Sie bei dieser Konstellation eine andere Art der Synchronisation. Der Producer könnte beispielsweise über ein Flag mitteilen, dass ein neues Datum vorhanden ist. Der Consumer kann über ein zweites Flag mitteilen, dass er seine Daten verarbeitet hat und nun auf neuen Input wartet. Beide können so auf den gleichen Daten arbeiten. Es entfällt der Overhead für das zyklische Kopieren der Daten und der Consumer verliert keine Daten, die der Producer erzeugt hat.
Monitoring
Zur Laufzeit existieren von der tasklokalen Variablenliste mehrere unter Umständen verschiedene Kopien im Speicher. Beim Monitoring einer Position können aber nicht alle Werte angezeigt werden. Deshalb werden beim Inline-Monitoring, in der Überwachungsliste und in der Visualisierung für eine tasklokale Variable die Werte aus der gemeinsamen Referenz angezeigt.
Wenn Sie einen Haltepunkt setzen, werden die Daten derjenigen Task angezeigt, die auf den Haltepunkt gelaufen ist und folglich angehalten wurde. Währenddessen laufen aber die anderen Tasks weiter. Dabei kann unter Umständen die gemeinsame Kopie geändert werden. Im Kontext der angehaltenen Task bleiben die Werte aber unverändert und werden so angezeigt. Dessen müssen Sie sich bewusst sein.
Hintergrund: Technische Umsetzung
Der Compiler legt für eine Liste von tasklokalen Variablen eine Kopie für jede Task, sowie eine gemeinsame Referenzkopie für alle Tasks an. Dabei wird eine Struktur erzeugt, die die gleichen Variablen beinhaltet wie die Liste der tasklokalen Variablen. Außerdem wird ein Array mit dieser Struktur angelegt, wobei für jede Task eine Arraydimension erzeugt wird. Somit wird für jede Task ein Arrayelement indiziert. Wenn nun im Code auf eine Variable der Liste zugegriffen wird, wird tatsächlich auf die tasklokale Kopie der Liste zugegriffen. Außerdem wird ermittelt, in welcher Task der Baustein gerade läuft und der Zugriff entsprechend indiziert.
Beispielsweise wird die Codezeile diValue := TaskLocals.g_diaData[0];
aus dem obigen Beispiel ersetzt:
diValue := __TaskLocalVarsArray[__CURRENTTASK.TaskIndex].__g_diarr[0];
__CURRENTTASK
ist ein Operator, der ab CODESYS V3.5 SP13 zur Verfügung steht, um den aktuellen Taskindex schnell zu ermitteln.
Zur Laufzeit wird am Ende der schreibenden Task der Inhalt der tasklokalen Liste in die gemeinsame Referenz kopiert. Bei einer lesenden Task wird zu Beginn der Inhalt der gemeinsamen Referenz in die tasklokale Kopie kopiert. Deshalb gibt es für n Tasks n+1 Kopien der Liste: Eine Liste dient als gemeinsame Referenz und zusätzlich hat jede Task eine eigene Kopie der Liste.
Ein Scheduler steuert die zeitliche Ausführung mehrerer Tasks und damit die Taskumschaltung. Die Strategie, die vom Scheduler verfolgt wird, um die Zuteilung der Ausführungszeit zu steuern, zielt darauf ab, das Blockieren einer Task zu vermeiden. Der Synchronisationsmechanismus ist also auf die Eigenschaften tasklokaler Variablen hin optimiert, dass blockierende Zustände (Lockzustände) vermieden werden und zu keinem Zeitpunkt eine Task auf die Aktion einer anderen Task wartet.
Synchronisationsstrategie:
-
Solange die schreibende Task eine Kopie auf die gemeinsam Referenz zurückschreibt, holt sich keine der lesenden Tasks eine Kopie.
-
Solange eine lesende Task eine Kopie von der gemeinsamen Referenz holt, schreibt die schreibende Task keine Kopie zurück
Anleitung zum Erstellen der oben beschriebenen Beispielapplikation
Ziel: Sie wollen mit einem Programm ReadData
auf diesselben Daten zugreifen, die von einem Programm WriteData
geschrieben werden. Die beiden Programme sollen in unterschiedlichen Tasks laufen.
Die Daten stellen Sie in einer tasklokalen Variablenliste bereit, damit sie automatisch
zykluskonsistent bearbeitet werden.
Voraussetzung: Ein Standardprojekt ist frisch angelegt und im Editor geöffnet.
-
Benennen Sie die Applikation von
Application
inappTasklocal
um. -
Fügen Sie unter
appTasklocal
ein Programm in ST mit NamenReadData
hinzu. -
Fügen Sie unter
appTasklocal
ein weiteres Programm in ST mit NamenWriteData
hinzu. -
Benennen Sie die Standardtask
MainTask
unter dem ObjektTaskkonfiguration
inRead
um. -
Fügen Sie im Dialog „Konfiguration“ der Task
Read
über die Schaltfläche „Aufruf hinzufügen“ den Aufruf des ProgrammsReadData
hinzu. -
Fügen Sie unter dem Objekt „Taskkonfiguration“ eine weitere Task hinzu, benannt mit
Write
und fügen dieser Task den Aufruf des ProgrammsWrite
hinzu.In der Taskkonfiguration gibt es nun zwei Tasks
Write
undRead
, die die ProgrammeWriteData
beziehungsweiseReadData
aufrufen. -
Selektieren Sie die Applikation
appTasklocal
und fügen Sie ein Objekt des Typs „Globale Variablenliste (tasklokal)“ hinzu.Der Dialog „Globale Variablenliste (tasklokal) hinzufügen“ öffnet sich.
-
Geben Sie als Namen
Tasklocals
ein. -
Wählen Sie aus der Auswahlliste „Task mit Schreibzugriff“ die Task
Write
.Die Objektstruktur für die Verwendung von tasklokalen Variablen innerhalb einer Applikation ist vollständig. Sie können die Objekte nun codieren, wie oben in der Beschreibung des Beispiels gezeigt.