How-to guide
Process many values efficiently
Read, write, and subscribe to thousands of values with one call: how the SDK adapts large requests to the limits of the server, and what pays off when.
How-to guide
Process many values efficiently
Each call to ReadNode or WriteNode is a separate request to the server with its own network round trip. If you read many values one by one in a loop, you quickly lose seconds. Instead, the methods ReadNodes, WriteNodes, and SubscribeNodes accept any number of commands in one call. The SDK splits them into requests that the server accepts, and it reassembles the responses in the order of the commands.
Read many values in one call
Types usedOpcClientOpcReadNodeOpcValue
The results are in the same order as the commands. Evaluate the status of each value separately. An invalid node does not abort the call:
var commands = new OpcReadNode[5000];
for (int index = 0; index < commands.Length; index++)
commands[index] = new OpcReadNode("ns=2;s=Sensors/Sensor" + (index + 1));
IEnumerable<OpcValue> values = client.ReadNodes(commands);Dim commands = New OpcReadNode(4999) {}
For index As Integer = 0 To commands.Length - 1
commands(index) = New OpcReadNode("ns=2;s=Sensors/Sensor" & (index + 1))
Next
Dim values As IEnumerable(Of OpcValue) = client.ReadNodes(commands)Write many values in one call
Types usedOpcClientOpcWriteNodeOpcStatusOpcStatusCollection
Writing also returns one result per command. Use the index to match each result to its command:
var commands = new OpcWriteNode[5000];
for (int index = 0; index < commands.Length; index++)
commands[index] = new OpcWriteNode("ns=2;s=Setpoints/Setpoint" + (index + 1), value: 0.0);
OpcStatusCollection results = client.WriteNodes(commands);
for (int index = 0; index < results.Count; index++) {
if (results[index].IsBad)
Console.WriteLine("{0}: {1}", commands[index].NodeId, results[index].Description);
}Dim commands = New OpcWriteNode(4999) {}
For index As Integer = 0 To commands.Length - 1
commands(index) = New OpcWriteNode("ns=2;s=Setpoints/Setpoint" & (index + 1), value:=0.0)
Next
Dim results As OpcStatusCollection = client.WriteNodes(commands)
For index As Integer = 0 To results.Count - 1
If results(index).IsBad Then
Console.WriteLine("{0}: {1}", commands(index).NodeId, results(index).Description)
End If
NextHow the SDK splits large requests
Types usedOpcClientOpcClientServices
Servers limit how many nodes a single request can contain and report these limits under Server/ServerCapabilities/OperationLimits. If a call contains more than one command, the client proceeds as follows:
- On the first such call, it reads the limit of the server for the respective service (
MaxNodesPerRead,MaxNodesPerWrite,MaxNodesPerBrowse, andMaxNodesPerTranslateBrowsePathsToNodeIds). If the server reports no limit (0), the client uses 1000. - If you set your own limit for the service, the smaller of the two values applies.
- The client remembers the value and then splits all further calls into multiple requests. It reassembles the responses in the order of the commands.
This applies to ReadNodes, WriteNodes, browsing, and TranslatePaths. A lower limit of your own makes sense if single requests should take less time, or if the server fails on response sizes despite its reported limit:
client.Services.ReadNodes.MaxNodesPerRead = 500;
client.Services.WriteNodes.MaxNodesPerWrite = 500;
client.Services.Browse.MaxNodesPerBrowse = 100;client.Services.ReadNodes.MaxNodesPerRead = 500
client.Services.WriteNodes.MaxNodesPerWrite = 500
client.Services.Browse.MaxNodesPerBrowse = 100The corresponding property with the prefix Used shows which limit the client actually uses. It is null as long as no call with multiple commands has taken place:
uint? nodesPerRead = client.Services.ReadNodes.UsedMaxNodesPerRead;Dim nodesPerRead As UInteger? = client.Services.ReadNodes.UsedMaxNodesPerReadRegardless of the number of nodes, the client limits the size of the data transferred. The section Data transfer limits describes how to raise these limits.
Subscribe to many nodes
Types usedOpcClientOpcSubscribeDataChangeOpcSubscriptionOpcMonitoredItem
If the values change rarely compared to the read interval, a subscription is cheaper than cyclic reading, because the server reports only changes. SubscribeNodes creates all MonitoredItems in one subscription. In the handler, the sender is the MonitoredItem. Use its NodeId to match the notification:
var commands = new OpcSubscribeDataChange[1000];
for (int index = 0; index < commands.Length; index++) {
commands[index] = new OpcSubscribeDataChange(
"ns=2;s=Sensors/Sensor" + (index + 1),
HandleDataChanged);
}
OpcSubscription subscription = client.SubscribeNodes(commands);
...
private static void HandleDataChanged(
object sender,
OpcDataChangeReceivedEventArgs e)
{
OpcMonitoredItem item = (OpcMonitoredItem)sender;
Console.WriteLine("{0}: {1}", item.NodeId, e.Item.Value);
}Dim commands = New OpcSubscribeDataChange(999) {}
For index As Integer = 0 To commands.Length - 1
commands(index) = New OpcSubscribeDataChange( _
"ns=2;s=Sensors/Sensor" & (index + 1), _
AddressOf HandleDataChanged)
Next
Dim subscription As OpcSubscription = client.SubscribeNodes(commands)
...
Private Shared Sub HandleDataChanged( _
ByVal sender As Object, _
ByVal e As OpcDataChangeReceivedEventArgs)
Dim item As OpcMonitoredItem = CType(sender, OpcMonitoredItem)
Console.WriteLine("{0}: {1}", item.NodeId, e.Item.Value)
End SubChoose the right access method
| Situation | Recommendation |
|---|---|
| Values are needed once or rarely | ReadNodes with all nodes in one call |
| The same nodes are read or written cyclically | Register the nodes and call ReadNodes or WriteNodes with the registered identifiers, see Register frequently read nodes |
| Values change rarely, the application reacts to changes | Subscription with all nodes through SubscribeNodes, see Subscriptions |
| Very large single values (binary data) | Read the values as a stream, see Large binary values as a stream |
| Nodes are only known by their path | Resolve them once with TranslatePaths, see Find nodes by path |

