Advanced programming
Multi-threading is an important aspect in iPlus programming. Always try to parallelize tasks whenever possible for the following reasons:
Conversely, multi-threaded programming means that access to shared resources must be protected by thread locks . In addition, the use of thread locks harbors the risk of deadlocks, which is a particular challenge for many programmers.
In the iPlus framework, however, some mechanisms are implemented that help you to implement the aforementioned problems and challenges more easily:
The gip.core.datamodel.ACThread class is a class that contains a encapsulated .NET thread class. Always use this class, so that the iPlus runtime is able to generate performance statistics. Statistics are created internally via a static instance of the "gip.core.datamodel.PerformanceLogger" class. You output these thread statistics with the RuntimeDump class.
The following example shows how to instantiate and use the ACThread class:
You always need your own threads if you want to call a certain code cyclically at the same time intervals. However, if you have different code that you just want to delegate to another thread so that the calling thread is not blocked, then use the "gip.core.datamodel. ACDelegateQueue" class. ACDelegateQueue is comparable to the task class in ".NET". ACDelegateQueues, however, have the advantage that only one explicit ACThread is used and that the tasks are placed in a queue that are processed again in the same order. Delegate queues can be diagnosed using RuntimeDump to create performance statistics by using the ACThread-class. In addition, ACDelegateQueues can also be stopped and restarted ("RestartQueue()" method).
ACDelegateQueue is also the basic class of database queues that you have already got to know in the database chapter.
The following example shows you how to use ACDelegateQueues:
The work thread in an ACDelegateQueue only ever becomes active as soon as a new task is placed in the queue. It spends the rest of the time sleeping so that computing power is not used up unnecessarily.
If you want to use in the ACThread like in the first example and do not want to that the method "DoSomething()" is called cyclic (even if it is nothing to do), then use instead ManualResetEvent the class gip.core.datamodel. SyncQueueEvents:
The use of thread locks, however, harbors the risk of deadlocks. The larger the code sections and the deeper the call stacks that a thread lock encompasses, the higher the risk. Therefore, when programming, you should always remember to keep the "critical sections" as short as possible.
The annoying thing about deadlocks is that they are discovered late and usually when the software is running in productive operation. In this situation only a restart of the service helps and this is often a very critical thing, depending on the area of application.
For this reason there is the class gip.core.datamodel.ACMonitor, which has the following advantages:
The concept of the .NET Monitor class provides that a field of type object is declared and passed when the Enter method is called. When using the ACMonitor class, pass an ACMonitorObject instead . The ACMonitorObject constructor requires an int value to be passed that indicates the lock level . Example:
public readonly ACMonitorObject _10020_LockValue = new ACMonitorObject (10020);
The number that you assign here should be as unique as possible and the size of the number should be adapted to the application level.
In iPlus, it is organized so that it is a five-digit number. The first digit corresponds to the assembly in which it is used. The smaller the number, the deeper the application layer:
Datalayer:
gip.core.datamodel: 10000
gip.mes.datamodel: 11000
Runtimelayer:
gip.core.autocomponent: 20000
Communicationlayer with other systems:
gip.core.communication: 30000
Managerlayer:
*.manager.dll: 40000
Applicationlayers:
*.processapplication*.dll: 60000
*bso*dll: 70000
The second to fifth positions are used for further level classification within the assemblies.
If you now use the lock objects, then a lock object from a lower level must never be used in front of a lock object from a higher level!
To prevent this from happening you need the ACMonitor class. It is used as follows:
using (ACMonitor.Lock(_11020_LockValue))
{
// Not allowed to use a lock with a higher number than 11020:
// SynchronizationLockException with be thrown in Debugger;
// otherwise the stacktrace dumped into the logfile
using (ACMonitor.Lock(_20010_LockValue))
{
}
}
In the above case, you as the programmer will be informed that you have not adhered to the lock hierarchy and that this is a potential danger of deadlock.
The notification is made in two ways:
The check of the lock hierarchies is activated using "static bool ACMonitor.ValidateLockHierarchy". ValidateLockHierarchy is either activated in the application config file in the CoreConfiguration section or you set this property directly in your application code .
Using lock hierarchies, you have already reduced the risk of a future deadlock situation enormously in the development phase. However, you can never fully simulate the dynamic behavior during the development phase. In productive operation, a lot comes together at once:
You can use the "ValidateLockHierarchy" switch to output hierarchy violations in productive operation, but what happens if a deadlock occurs anyway?
You have to kill the iPlus service in the task manager because the two threads involved can no longer run and "Thread.Join()" cannot be completed even when shutting down. In addition, an abrupt termination of the iPlus service can have fatal consequences for business processes (especially in real-time industrial systems). Here it must be weighed up which problem is more critical:
Your decision is already obvious here: Option A - You accept the inconsistent state because it can be restored through the many possibilities for influencing it via the client interface. Activate the "UseSimpleMonitor = false" switch for a certain period of time (eg for a few weeks) so that the entire system can go through a maturity phase or a stabilization phase.
This setting means that in the event of a deadlock, a SynchronizationLockException is thrown and diagnostic and stack information is output in the message log. The deadlock does not take place at all, but the call stack that could have led to the deadlock is resolved by the exception. The critical code sections are therefore not run through completely and the corresponding instance is then in an inconsistent state. The output in the message log must be examined here and forwarded to the responsible developer so that the program can be changed.
If the system has run deadlock-free for some time (without entries in the message log ), set UseSimpleMonitor to true . With this you declare that the "maturity phase" has been completed and that the system is now running stable. The result of this change is that the computationally expensive deadlock detection mechanism is deactivated and the efficient monitor class is used internally instead.
We use cookies to provide you with the best possible experience. They also allow us to analyze user behavior in order to constantly improve the website for you.