- Introduction
- Example details
- Preconditions
- Project compiling in Eclipse
- PLCnext Engineer project
- Start-Up instructions
- Manually basic Setup
- Commons::WorkerThread and Commons::Thread
- General Notes
This example demonstrates the creation and usage of workerThread, staticThread and delegateThread in a C++ application. All threads are implemented in a component in a non real-time context.
The purpose of workerThread implementation is to relocate time-consuming blocking operation from the real-time program into a separate thread, because otherwise this would have an unwanted impact on the real-time process.
The purpose of staticThread implementation is to start a non real-time task, e.g. methods of RSC services.
The purpose of the delegateThread implementation is to use a call-back function (the thread has access to this*).
Description | Value |
---|---|
Controller | AXC F 2152 |
FW | 2020.0 LTS or later |
SDK | 2020.0 LTS or later |
PLCnext Engineer | 2020.0 LTS or later |
- AXC F 2152 controller with firmware 2020.0 LTS or later
- Eclipse IDE "Photon" or later
- PLCnext Engineer 2020.0 LTS or later
- In Eclipse, create the project "ThreadExample" with Component "ThreadExampleComponent" and Program "ThreadExampleProgram".
- In the project, replace the files "ThreadExampleComponent.cpp", "ThreadExampleComponent.hpp", "ThreadExampleProgram.cpp", and "ThreadExampleProgram.hpp" with the according files from this repository. Alternatively, you can create your own project with component and programs and include the source code in your application.
- Compile the Eclipse project.
- After successfull project compilation, the PLCnext Engineer library will be created automatically. You can find it in your Eclipse workspace folder, e.g.: "workspace\ThreadExample\bin\ThreadExample.pcwlx"
- In PLCnext Engineer, create a new project and include the "ThreadExample.pcwlx" in the project.
- Instantiate the "ThreadExampleProgram" under a previously defined task.
- Declare a port variable of "INT" datatype, e.g. in the "main" program.
- In the "PLANT" area of the PLCnext Engineer, unfold the "PLCnext" node and connect the C++ port variable "i_pCounter" and the IEC 61131 port variable.
- Download the PLCnext Engineer project to the PLCnext Control.
- If the project is successfully implemented and downloaded, you can follow the value incrementation of variable "i_pCounter" in PLCnext Engineer debug mode and/or in a watchwindow and threads status in the Output.log file on the plcnext target; see: /opt/plcnext/logs/Output.log
- If the project does not start successfully, please see the error messages in Output.log file on the PLCnext target, see: /opt/plcnext/logs/Output.log
- Add
#include "Arp/System/Acf/IControllerComponent.hpp"
and inherit theIControllerComponent
interface. - Implement the
Component.Start()
andComponent.Stop()
method. - Include the threading headers:
#include "Arp/System/Commons/Threading/WorkerThread.hpp"
#include "Arp/System/Commons/Threading/Thread.hpp"
#include "Arp/System/Commons/Threading/ThreadSettings.hpp"
1.) Create a workerThreadInstance
, delegateThreadInstance
and staticThreadInstance
:
WorkerThread workerThreadInstance;
Thread delegateThreadInstance;
Thread staticThreadInstance;
2.) Create a private methods
with parameters, that implements the threads bodys - what should be done in this threads.
//... Header
bool xStopThread = false;
int myparameter{123};
private: void WorkerThreadBody(void);
void delegateThreadBody(void * pParameter);
static void staticThreadBody(void* pParameter);
3.) Initialize the WorkerThread
, delegateThread
and staticThread
in your Components constructor:
ThreadExampleComponent::ThreadExampleComponent(IApplication& application, const String& name)
: ComponentBase(application, ::ThreadExample::ThreadExampleLibrary::GetInstance(), name, ComponentCategory::Custom)
, programProvider(*this)
, ProgramComponentBase(::ThreadExample::ThreadExampleLibrary::GetInstance().GetNamespace(), programProvider)
// Added: data info provider
, dataInfoProvider(::ThreadExample::ThreadExampleLibrary::GetInstance().GetNamespace(), &(this->programProvider))
//// Worker Thread Example
, workerThreadInstance(make_delegate(this, &ThreadExampleComponent::workerThreadBody) , 10000, "WorkerThreadName")
//// Delegate Thread Example
, delegateThreadInstance(ThreadSettings("DelegateThreadName", 20, 0, 0),this,&ThreadExampleComponent::delegateThreadBody,(void*)&myparameter)
//// Static Thread Example
, staticThreadInstance(ThreadSettings("StaticThreadName", 20, 0, 0),&ThreadExampleComponent::staticThreadBody,(void*)&xStopThread)
4.) Add workerThreadInstance.Start()
, workerThreadInstance.Stop()
, delegateThreadInstance.Start()
, staticThreadInstance.Start()
to the Component. The Start method is used to start or stop the threads during start of the controller or/and component, respectively.
The Thread body will be executed, and after this, the thread sleeps for the specified idle time. The WorkerThread.Stop()
method blocks until the thread finishes its current work, if it is not sleeping.
When component Stop
is called, the staticThread or delegateThread will be synchronously executed one last time and started again by the Start
component.
void ThreadExampleComponent::Start(void) {
xStopThread = false;
Log::Info("-------------------------------workerThreadInstance start");
workerThreadInstance.Start();
Log::Info("-------------------------------workerThreadInstance started");
Log::Info("-------------------------------delegateThreadInstance start");
delegateThreadInstance.Start();
Log::Info("-------------------------------delegateThreadInstance started");
Log::Info("-------------------------------staticThreadInstance start");
staticThreadInstance.Start();
Log::Info("-------------------------------staticThreadInstance started");
}
void ThreadExampleComponent::Stop(void) {
// if you want to stop some loops of your thread during execution
// add something like "stoptheThread" before executing workerThreadStop.
xStopThread = true;
Log::Info("-------------------------------workerThreadInstance stop");
workerThreadInstance.Stop();
Log::Info("-------------------------------workerThreadInstance stopped");
}
/// worker Thread Body
void ThreadExampleComponent::workerThreadBody(void) {
if(xStopThread == false)
{
if(iCountervalue < iEndValue)
iCountervalue ++;
else
iCountervalue = iStartValue;
Log::Info("-------------------------------workerThreadInstance is running, iCountervalue={0}", iCountervalue);
}
else
Log::Info("-------------------------------workerThreadInstance is stopped, iCountervalue={0}", iCountervalue);
}
/// delegate Thread Body (non-static)
void ThreadExampleComponent::delegateThreadBody(void* pParameter) {
// has access to this*
int *i = reinterpret_cast<int*>(pParameter);
while(!xStopThread)
{
if(this->myparameter == *i)
Log::Info("-------------------------------ThreadExampleComponent::delegateThreadBody is running successful i={0} , myparameter = {1}", *i, this->myparameter);
else
Log::Info("-------------------------------ThreadExampleComponent::delegateThreadBody is running with ERROR: i={0} , myparameter = {1}", *i, this->myparameter);
Thread::Sleep(1000);
}
Log::Info("-------------------------------ThreadExampleComponent::delegateThreadBody stopped");
}
/// static Thread Body
void ThreadExampleComponent::staticThreadBody(void* pParameter) {
bool* pValue = (bool*) pParameter;
// has NO access to this*
while(!*pValue){
//Do something and sleep for specifiedTime.
Log::Info("-------------------------------ThreadExampleComponent::staticThreadBody is running pParameter={0}", (int) *pValue);
Thread::Sleep(500);
}
Log::Info("-------------------------------ThreadExampleComponent::staticThreadBody stopped");
}
It is not recommended to instantiate threads inside a RT (real-time) program because the thread's lifetime management and controlling should be done by the component.
To exchange data between the real-time program and the worker thread, add the data portion (e.g. port variables or methods) to the component, and each instantiated program should obtain a pointer to its component by adjusting the program provider (see method Program::Execute() in ThreadExampleProgram.cpp).
Please remember to make your data exchange structure thread safe, otherwise the data exchange will result in undefined behavior. Also consider about the cycle time of your Program::Execute() if you decide to use blocking mechanisms to guarantee thread safety.