Sie sind auf Seite 1von 29

Optimizing your PI SDK

Applications
OSIsoft vCampus White Paper

How to Contact Us
Worldwide Offices
Email: vCampus@osisoft.com
Web: http://vCampus.osisoft.com > Contact Us

OSIsoft Australia Pty Ltd.


Perth, Australia
Auckland, New Zealand

OSIsoft, LLC

OSIsoft Europe GmbH

777 Davis St., Suite 250


San Leandro, CA 94577 USA

Frankfurt am Main, Germany

OSI Software Asia Pte Ltd.


Singapore

Houston, TX
Johnson City, TN
Mayfield Heights, OH
Phoenix, AZ
Savannah, GA
Seattle, WA
Yardley, PA

OSIsoft Canada ULC


Montreal, Quebec
Calgary, Alberta

OSIsoft, LLC. Shanghai


Shanghai, Peoples Republic of China

OSIsoft Japan KK
Tokyo, Japan

OSIsoft Mexico S. De R.L. de C.V.


Mexico City, Mexico

Sales Outlets and Distributors

Brazil
Middle East/North Africa
Republic of South Africa
Russia/Central Asia

South America/Caribbean
Southeast Asia
South Korea
Taiwan

WWW.OSISOFT.COM
OSIsoft, LLC is the owner of the following trademarks and registered trademarks: PI System, PI
ProcessBook, Sequencia, Sigmafine, gRecipe, sRecipe, and RLINK. All terms mentioned in this book
that are known to be trademarks or service marks have been appropriately capitalized. Any trademark
that appears in this book that is not owned by OSIsoft, LLC is the property of its owner and use herein
in no way indicates an endorsement, recommendation, or warranty of such partys products or any
affiliation with such party of any kind.
RESTRICTED RIGHTS LEGEND
Use, duplication, or disclosure by the Government is subject to restrictions as set forth in subparagraph
(c)(1)(ii) of the Rights in Technical Data and Computer Software clause at DFARS 252.227-7013
Unpublished rights reserved under the copyright laws of the United States.
1998-2011 OSIsoft, LLC

TABLE OF CONTENTS
Table of Contents ............................................................................................................................... ii
Overview ........................................................................................................................................... 1
About this Document ............................................................................................................................. 1
What You Need to Start ......................................................................................................................... 1
The OSIsoft SDKs ................................................................................................................................ 2
What are the OSIsoft SDKs? ................................................................................................................... 2
What OSIsoft SDKs are available? .......................................................................................................... 2
Other products and Data Access technologies....................................................................................... 2
Programming with the PI SDK............................................................................................................. 3
Optimization Techniques........................................................................................................................ 5
Connection Pooling (ServerManager).............................................................................................. 5
Asynchronous Calls .......................................................................................................................... 8
ListData Functions .......................................................................................................................... 14
Event Pipes ..................................................................................................................................... 14
Multithreading Applications .......................................................................................................... 15
Multi-Threaded NamedValue (MTNV) and Multi-Threaded NamedValues (MTNVS ) .................. 18
IPIValues2.GetValueArrays ............................................................................................................ 20
Conclusion ....................................................................................................................................... 23
Resources ........................................................................................................................................ 24
Revisions ......................................................................................................................................... 25

ii

OVERVIEW
ABOUT THIS DOCUMENT
This document is exclusive to the OSIsoft Virtual Campus (vCampus) and is available on its
online Library, located at http://vCampus.osisoft.com/Library/library.aspx. As such, it is
provided 'as is' and is not supported by OSIsoft's regular Technical Support.
Any question or comment related to this document should be posted in the appropriate
vCampus discussion forum (http://vCampus.osisoft.com/forums) or sent to the vCampus
Team at vCampus@osisoft.com.

About this White Paper

This document contains a loose collection of various examples that can prove useful to
optimize custom PI SDK applications for high performance and scalability.

WHAT YOU NEED TO START

Visual Studio 2008 or higher


o All samples in the paper are written in C#
PI SDK 2010 R2 or higher
PI Server 2010 or higher

THE OSISOFT SDKS


WHAT ARE THE OSISOFT SDKS?
The OSIsoft SDKs are libraries and shared components that allow developers to utilize our
proprietary technology in their applications, and allow them to create customized
interfaces, add user interactions, new functionalities to existing products, expose data in
new formats, perform advanced calculations, generate analysis, export reports, etc.

WHAT OSISOFT SDKS ARE AVAILABLE?


Being a member of the OSIsoft vCampus program, you have the development license of our
various SDKs, each of these pertaining to a specific area of the PI System Architecture. The
following SDKs are available:

PI SDK PI Server
o PI Points Attributes and Data
o PI Server Message Log
o Various PI Databases (Users & Groups, etc.)
AF SDK AF Server and PI Notifications
o AF Databases, Elements, Attributes
o AF Analysis Rules (ARs) and Data References (DRs)
o AF Connectivity Models and Cases
o Notification Configuration (including custom Delivery Channels)
o Notification instances
o Event Frames

OTHER PRODUCTS AND DATA ACCESS TECHNOLOGIES


There is a plethora of other Data Access technologies and programming tools provided by OSIsoft
which may fit your needs, please consider using some of the following:

Data Access Technologies


o PI Web Services
o PI OLEDB Provider
o PI OLEDB Enterprise
o PI OPC DA/HDA Server
o PI JDBC Driver
Other Products
o PI DataLink & PI DataLink Server
o PI Interfaces
o PI ACE (Advanced Computing Engine)
o PI for StreamInsight
o PI ProcessBook & Visual Basic for Applications

PROGRAMMING WITH THE PI SDK


There are four .NET assemblies (installed in the GAC Global Assembly Cache) that can to be added
as reference to all PI SDK .NET applications:

OSIsoft.PISDK

OSIsoft.PISDKCommon

OSIsoft.PISDKDlg (Common dialog windows such as TagSearch, Connections, etc.)

OSISoft.PITimeServer (PI Time Functions and conversions)

You can find those either in the .NET tab in VS2008 or your Recent tab in VS2010, if you cant find
them in those tabs you can go to the Browse tab and navigate your way to the PIPC\PISDK and
PIPC\LIBRARY folders.

.NET BASED PISDK CONTROLS LIBRARY


PI SDK has a list of .NET based controls and dialogs (part of OSISoft.PISDK.Controls.dll) that you can
deploy directly in .NET form applications, including:

Connection Manager control (ConnectionManager)

Server Pick List control (PIServerPickList)

Snapshot control (PISnapShotCtl)

Archive Editor control (PIArchiveEditor)

PI SDK Buffering configuration dialog (BufferingSetupForm)

These controls and dialogs can be inserted into your form applications directly. To add the controls
into the toolbox on Visual Studio, you can right-click on the toolbox and select Choose Item

From there a dialog window will appear for you to choose the controls to include in the toolbox. If
you look for the OSIsoft.PISDK.Controls under Assembly Name, you should find these controls

As for the dialogs like PI SDK Buffering Configuration dialog, you can instantiate the control and use
ShowDialog method to bring up the dialog box
OSIsoft.PISDKControls.BufferingSetupForm bsf = new
OSIsoft.PISDKControls.BufferingSetupForm();
DialogResult dr = bsf.ShowDialog();

OPTIMIZATION TECHNIQUES
CONNECTION POOLING (SERVERMANAGER)
Characteristics

Single sign on inherits callers Windows identity


Supports PITrust and PIIdentity (Interactive Login not supported)
Designed for web-based applications with multiple users and multiple PI Servers

Description
The ServerManager is a collection object whose purpose is to provide a reusable pool of open server
connections to PI Servers version 3.3 (and higher). A good use case for this is web-based
applications (such as PI WebParts), which can provide improved performance while maintaining
security by creating a single ServerManager as an application level object. The ServerManager can
use either the PITrust table, maintained on the PI Server, to validate connected domain users or the
preferred windows authentication and provides open connections for validated users. The
ServerManager does not support explicit logins.
Typical PI SDK client/server applications maintain a small number of connections over the execution
of the application, reaping the benefits of cached data. In a "connectionless" Web environment,
reestablishing a PI SDK hierarchy with each client access is prohibitively slow. The ServerManager
solves this problem by storing open Server objects and providing secure access to them as required.
In the past, applications were able to manage the data cached by the PI SDK using the Refresh
method in the IRefresh interface on various members of the hierarchy, this can be done for Server,
PIUsers, PIPoints, PIProperties, PointAttributes, amongst others. In addition, the ServerManager can
be directed to delete connections that are not currently in use to manage memory and network
resources via the SlimFast method.
Example
This example is an active server page (ASP.NET) that uses a ServerManager object stored in the ASP
Application collection. Users connect to the page with a web browser and select a server and a tag
whose snapshot value is then displayed. The connection is made using a PI Trust and the resulting PI
User is also displayed.
This example is built by creating two pages in a virtual directory under Internet Information Server
(IIS).
0. Create a Web Application in your favorite language (Example granted in C#, Web site
solution named ServerManager_Demo).
1. Open the file called "global.asax". This page is the global code available to all ASP.NET pages
in this directory. Locate section "Application_Start" and insert the following code there:
Application["srvrmgr"] = new PISDK.ServerManager();

2. Now create the aspx page that the user will browse. Name it something appropriate (e.g.
servermgr.aspx). Insert the following code in the page.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="servermgr.aspx.cs"


Inherits="ServerManager_Demo.servermgr" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>ServerManager Demo</title>
</head>
<body>
<font color="royalblue" size="4" face="Lucida Sans Unicode" ><strong>Server Manager
Test</strong></font><br>
<form id="form2" runat="server">
<P><font color="purple">Select Server<br /></font>
<asp:DropDownList runat="server" id="select1" style="height: 22px; width: 142px;">
<asp:ListItem Value="Server1" Selected="True">Server1</asp:ListItem>
<asp:ListItem Value="Server2">Server2</asp:ListItem>
<asp:ListItem Value="Server3">Server3</asp:ListItem>
<asp:ListItem Value="Server4">Server4</asp:ListItem>
<asp:ListItem Value="Server5">Server5</asp:ListItem>
</asp:DropDownList></P>
<p><font color="purple">
Select Tag:<br /></font>
<asp:DropDownList runat="server" ID="select2" style="HEIGHT: 22px; LEFT: 152px; TOP:
91px; WIDTH: 173px">
<asp:ListItem Value="sinusoid" Selected="True">sinusoid</asp:ListItem>
<asp:ListItem Value="cdm158">cdm158</asp:ListItem>
<asp:ListItem Value="cdt158">cdt158</asp:ListItem>
<asp:ListItem Value="sinusoidu">sinusoidu</asp:ListItem>
<asp:ListItem Value="ba:active.1">ba:active.1</asp:ListItem>
</asp:DropDownList></p>
<p>
<input type="hidden" name="srvrindx" />
<input type="hidden" name="tagindx" />
<input type="hidden" name="firstTime" value="0" />
<asp:Button ID="Button1" runat="server"
Text="Get Snapshot" />
</p>
<hr />
<font color="purple">
Server: <span runat="server" id="spanSrvrName" style="color: green"></span></font><font
color="purple"> Tag Name: </font><span runat="server" id="spanTagName" style="color:
green"></span><br /><hr />
Current snapshot value is: <span runat="server" id="spanSnapshot" class="foo"
style="color: red"></span><br />
Current logged on user is: <span runat="server" id="spanUser" class="foo" style="color:
blue"></span><br />
Current windows user is: <span runat="server" id="spanWinUser" class="foo"
style="color: Green"></span><br />
<span runat="server" style="color: Red;" id="spanError"></span>
</form>
</body>
</html>

3. In the code behind page (named servermgr.aspx.cs if you have followed this examples
namings) insert the following code in the Page Load section:
string snapvalue, piuser, tagName, srvrName;
{
if (Page.IsPostBack)
{
try
{
PISDK.ServerManager srvrmgr;
srvrmgr = (PISDK.ServerManager) Application["srvrmgr"];
PISDK.Server srvr;

srvrName = select1.SelectedValue;
srvr = srvrmgr[srvrName];
PISDK.PIPoint pt;
tagName = select2.SelectedValue;
pt = srvr.PIPoints[tagName];
PISDK.PIValue val;
val = pt.Data.Snapshot;
snapvalue = val.Value.ToString();
piuser = srvr.CurrentUser;
//fill the html part
spanSrvrName.InnerText = srvrName;
spanTagName.InnerText = tagName;
spanSnapshot.InnerText = snapvalue;
spanUser.InnerText = piuser;
spanError.InnerText = string.Empty;
if (User.Identity.IsAuthenticated)
{
spanWinUser.InnerText = User.Identity.Name;
}
else
{
spanWinUser.InnerText = "Not authenticated.";
}
}
catch (Exception err)
{
spanError.InnerText = err.Message;
}
}
}

4. The ASP.NET page has a form that causes a postback when the button is pressed. The PI
servers in the select box (shown above as Server1 - Server5) should reflect servers available
to your application as entries in the Known Servers Table. In addition these Servers should
be configured to use windows authentication for the users who will be viewing the page. If
the default settings for the ASP.NET page are not changed then it will run under the
Application pools identity, so this is the user you should grant access to the PI Server.
5. You can create a link or button on this ASP.NET page that links to a different page which
displays the current count of connections in the global ServerManager object and provides a
means to call SlimFast to remove connections that are not currently in use. You can build
this page by creating a file called "mngmnt.aspx" and copying in the following contents:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="mngmnt.aspx.cs"
Inherits="ServerManager_Demo.mngmnt" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Server Management</title>
</head>
<body>
<form id="form1" runat="server"><p>
Current server/username connection count is: <span runat="server"
id="spanCount"></span>
&nbsp;
<asp:Button ID="doSlimFast" runat="server" onclick="doSlimFast_Click"
Text="Slimfast" />

<asp:Button ID="goBack" runat="server" onclick="goBack_Click" Text="Back" />


</p>
</form>
</body>
</html>

6. And in the code behind page (named mngmnt.aspx.cs) paste the following code:
protected void Page_Load(object sender, EventArgs e)
{
spanCount.InnerText = ((PISDK.ServerManager)Application["srvrmgr"]).Count.ToString();
}
protected void doSlimFast_Click(object sender, EventArgs e)
{
((PISDK.ServerManager) Application["srvrmgr"]).SlimFast();
}
protected void goBack_Click(object sender, EventArgs e)
{
Server.Transfer("servermgr.aspx");
}

7. To fully secure access to the application, the virtual directory should be set to use
"Integrated Windows Authorization" in IIS. Using Windows Explorer, the ASP.NET file should
be made accessible only to the desired domain users. When configured in this manner,
browsers connecting to this page will be issued a challenge (often silently) and if the
authentication is successful the page will run in IIS impersonating the logged on user of the
browser. Behind the scenes, the PI SDK obtains the identity of the logged in user and passes
it to the PI Server. The PI Server validates this identity against the domain controller. This
means that users outside the domain and associated trusted domains cannot be validated
and will not be granted a connection.

ASYNCHRONOUS CALLS
Characteristics

Non-blocking data access calls


Designed to provide higher scalability by leveraging computing resources of the
PI Server.

Description
As you know, some programming operations take time. Imagine that you built a single-threaded
application that is invoking a method on a remote object, performing a long-running database query,
downloading a large document, or writing 1000 lines of text to an external file. While performing
these operations, the application will appear to hang for some amount of time. This happens
because until the task at hand has been processed, all other aspects of this program (such as menus,
toolbars, or console output) will be unresponsive. In order to prevent this lock up we can call a
function and instruct it to return the execution right away, making the interface responsive again.
This could be done calling the locking instruction in a separated thread or by calling it
asynchronously. When creating a different thread we can create a Delegate and sign up for an event
or we could check the status of the asynchronous call periodically until it has finished.

The PIAsynchStatus object supports Windows events that can be used to track progress and status of
an asynchronous call. These Windows events, however, are only reliably delivered in a single
threaded application. Though a multi-threaded application can still use polling, checking the status
to determine when a call is complete, this interface exposes an alternate way to detect call
completion across threads without having to poll.
Synchronization primitives, in this case a Windows Event object (event is a heavily overloaded term),
can be tracked using the Win32 call WaitForSingleObject. With this call you can control how long
you wait for an answer so you can spawn a thread that waits until the object is signaled or you can
check if it has been signaled periodically.
Another benefit of using the synchronization event to check for call completion is that you can make
several asynchronous calls and wait for all of them to return using the Win32 call
WaitForMultipleObjects. This call can be used to wait for an array of event objects to complete
before returning or can return when any of the objects has been signaled.
There are various methods you could use to check the status of the Asynchronous call, a simple loop
to wait for one or more Asynchronous Calls to finish, creating a delegate or signing up for an event.
Example
Simple Loop
This method will work for all the functions that currently support Asynchronous calling, for a
complete and updated list please visit the PISDK Help file in the Reference section of this
document.
PIAsynchStatus varPIAsynchStatus;
//Call your favorite method asynchronously
somePISDKClassMethodorInterface(, varPIAsynchStatus);
while (varPIAsynchStatus.Status != CallStatusConstants.csComplete ||
varPIAsynchStatus.Status != CallStatusConstants.csCompleteWithErrors)
{ /* Do something while waiting for Async call to complete */ }

Essentially this allows us the flexibility to perform other task that requires less processing while
waiting for the asynchronous call to complete in the background.
Another useful use case is also the flexibility to run multiple asynchronous calls concurrently. This
allows us to issue calls in batches instead of one after another. This is helpful when each call would
require a significant amount of processing time to complete for example getting the summaries
values of 1000 tags for a year with 1 month intervals. We can use loops to block the program to wait
for each batch to complete before progressing to the next step or issuing the next batch of
asynchronous calls. An example of such call:
int iMaxtags; // total point count to process
int iAsyncGroup = 10; // number of points in 1 batch
PointList _PointList = server.GetPoints("tag='cd*'"); // Get PointList from
PI Server
iMaxtags = _PointList.Count;
PISDKCommon.NamedValues[] _nvsSum = new PISDKCommon.NamedValues[iMaxtags];
PISDKCommon.PIAsynchStatus[] _PIAsynchStatus = new
PISDKCommon.PIAsynchStatus[iMaxtags];
PISDK.PIPoint[] _PIPoint = new PISDK.PIPoint[iMaxtags];
PISDK.IPIData2 _ipiD2;

for(int i = 0; i < iMaxtags - 1; i++)


{
_PIPoint[i] = _PointList[i + 1];
_PIAsynchStatus[i] = new PISDKCommon.PIAsynchStatus();
try
{
_ipiD2 = (IPIData2) _PIPoint[i].Data;?
_nvsSum[i] = _ipiD2.Summaries2("*-2h",
"*",
"",
PISDK.ArchiveSummariesTypeConstants.asTotal,
PISDK.CalculationBasisConstants.cbEventWeightedIncludeBothEnds,
_PIAsynchStatus[i]);
}
catch(Exception ex)
{
// print out error
return;
}
// after a batch of asynchronous call is issued block to wait for all
// the issue calls to complete before proceeding
int j = (i + 1) % iAsyncGroup;
if ((j == 0) || (i == iMaxtags - 1))
{
for(int k = (i + 1 - iAsyncGroup); k < i; k++)
{
while(_PIAsynchStatus[k].ProgressPercent < 100)
{
}
}
}
}
Console.WriteLine("Process completed");

Using a Delegate
The main function of Delegates is to allow generic calling of functions based purely on its signature.
So you could change what function gets executed or improve on the version that gets executed
overtime without having to make a change into any of the other code or the original function. Youll
just need to alter the declaration of the delegate and create the new function.
A delegate will also allow you to use any function in an asynchronous way whether it natively
supports it or not. The advantage of using delegates to do your asynchronous calls is that the .NET
compiler will take care of creating all the supporting methods: BeginInvoke, EndInvoke, and
WaitOne; it will also take care of updating the IsCompleted Attribute, which is part of the
IAsyncResult class. You can always tailor the way your Delegate behaves and add more specific
information to the State or the Delegate call back, but that is outside the scope of this whitepaper.
The BeingInvoke method is the responsible for calling the method asynchronously, we will need to
call the EndInvoke method to fetch the result of the asynchronous, well do that once the
IsCompleted Attribute gets set to true; finally WaitOne will give us a chance to wait for the function
to complete in a specified time lapse and if it does not it will return the execution to the main thread
allowing us to continue some work on this side while we wait for the method being called
asynchronously to finish.
10

Example
//Global variables
public delegate ReturnType DelegateName(type parameter1, , type parameterN);
//Next line needs to be static if this is used in a ConsoleApplication
DelegateName varDelegate;

//Main method
varDelegate = new DelegateName (varClass.DesiredMethod);
IAsyncResult varAsyncResult =
varDelegate.BeginInvoke(parameter1, , parameterN, null, null);
//WaitHandle maybe required in a WindowsForm application
WaitHandle varWaitHandle = varAsyncResult.AsyncWaitHandle;
while (!varAsyncResult.IsCompleted){
varWaitHandle.WaitOne(1); //Wait n milliseconds or continue execution
/* Do something while we wait for the Async call to complete */
}
ReturnType varResult = varDelegate.EndInvoke(varAsyncResult);

Using a Delegate and Event Callbacks


The main extension of this method is that adding an Event sign up for the event when doing the
BeingInvoke method, so instead of having to wait for it to return you can rely on the finalization
calling the method for you. The changes you need to do are: first we need to create a function that
will get called after the function has finished, second well add that function to the BeginInvoke call.
/* Delegate function to call when the asyncrhonous call finishes, you will
need to declare this as static if your project is a console application */
public void DelegateCallback(IAsyncResult ar) {
ReturnType varResult = varDelegate.EndInvoke(varAsyncResult);
/* You can now process this depending on the return type of the fuction you
excecuted */
}

//Main method
varDelegate = new DelegateName (varClass.DesiredMethod);
IAsyncResult varAsyncResult =
varDelegate.BeginInvoke(parameter1, , parameterN, new
AsyncCalBack(DelegateCallBack), null);
//You will only have to keep the application running.

Functions returning a PIValues object


PIData.RecordedValues

This method returns compressed values for the requested time range from the archive as a PIValues
collection.
PIData.PlotValues

The PlotValues method is designed to take in start time, end time and interval as parameter and
return a PIValues collection that will produce the most accurate plot while minimizing the amount of
data returned. For each interval, the data available is examined and significant values are
returned. Each interval can produce up to 5 values if they are unique, the value at the beginning of
11

the interval, the value at the end of the interval, the highest value, the lowest value and at most one
exceptional point (digital state). Usually interval setting is defined based on the number of pixels,
which gives an indication how much information is required to be display on the plot area.
IPICalculation.Calculate

This method returns a PIValues collection that contains the result of evaluating the passed
expression over the passed time range.
IPICalculation.TimedCalculate

This method returns a PIValues collection that contains the result of evaluating the passed
expression at the passed time points.
IPIData2.InterpolatedValues2

The method returns interpolated values from the archive over the specified time range as a PIValues
collection. InterpolatedValues2 differs from PIData.InterpolatedValues only in the specification of
the sampling interval. In PIData.InterpolatedValues, user specifies number of intervals and PISDK
computes the interval length based on start time, end time and the number of intervals. The interval
length is constant throughout the period. In InterpolatedValues2, user specifies the interval length
as a variant in the SampleInterval argument. The interval length could change within the requested
time period.
PIData.InterpolatedValues

The method returns interpolated values from the archive over the specified time range as a PIValues
collection. This method differs from IPIData2.InterpolatedValues2 only in how the sampling
intervals are specified. In InterpolatedValues, the caller specifies number of intervals and the PISDK
computes the interval length based on start time, end time and the number of intervals. The interval
length is constant throughout the period. In IPIData2.InterpolatedValues2, the caller specifies the
interval length as a variant in the SampleInterval argument. The interval length could change within
the requested time period.
PIData.TimedValues

This method returns a PIValues collection that contains the interpolated values from the archive at
the passed time stamps.
Example
//Getting data from a PIValues variable
foreach (PIValue tmpPV in tmpNV.Value) {
if (tmpNV.Value is DigitalState) sText = tmpPV.Value.Name;
if (tmpNV.Value is PITime) sText += tmpPV.Value.LocalDate.ToString();
if (tmpNV.Value is Double) sText += tmpPV.Value.ToString();
}

Functions returning a NamedValues Collection


IPICalculation.ExpressionSummaries

This method creates a data stream from an arbitrary expression and calculates one or more
summaries on the expression result. The method returns a NamedValues collection with the
requested summaries.
IPIData2.Summaries2

The Summaries2 method retrieves several summary types in a single call over the specified range,
and for each interval within the range. The method returns a NamedValues collection which contains
12

named summaries as PIValues. Summaries2 differs from PIData.Summaries only in the specification
of the summary calculation duration. In PIData.Summaries, user specifies number of calculation
intervals and PISDK computes the interval length based on start time, end time and the number of
intervals. The interval length is constant throughout the period. In Summaries2, user specifies the
interval length as a variant in the CalculationDuration argument. The interval length could change
within the requested time period.
IPIData2.FilteredSummaries

This method, when supplied a filter expression that evaluates to true or false, evaluates it over the
passed time range. For the time ranges where the expression evaluates to true, the method
calculates the requested summaries on the source point. The method returns a NamedValues
collection containing the results. This method is not supported for PI2 servers.
PIData.Summaries

The Summaries method retrieves several summary types in a single call over the specified range, and
for each interval within the range. The method returns a NamedValues collection which contains
named summaries as PIValues. The Summaries method differs from IPIData2.Summaries2 only in the
specification of the summary calculation duration. In PIData.Summaries, user specifies number of
calculation intervals and PISDK computes the interval length based on start time, end time and the
number of intervals. The interval length is constant throughout the period. In Summaries2, user
specifies the interval length as a variant in the CalculationDuration argument. The interval length
could change within the requested time period.
Example
//Getting data from a NamedValues variable
foreach (NamedValue tmpNV in varNamedValues) {
if(tmpNV.Value is PIValues) {
foreach (PIValue tmpPV in tmpNV.Value) {
if (tmpNV.Value is DigitalState) sText = tmpPV.Value.Name;
if (tmpNV.Value is PITime) sText += tmpPV.Value.LocalDate.ToString();
if (tmpNV.Value is Double) sText += tmpPV.Value.ToString();
}
}
}

Functions returning a PointList


Server.GetPoints

This method accepts a query string, and returns a PointList object that contains PIPoint objects
which satisfy the query. The query string syntax is a simplified form of SQL "where" clause.
Server.GetPointsSQL

Given a query string, this method returns a PointList collection that contains PIPoint objects which
match the request. The query string is in the format of a SQL "where clause.
Server.GetPoints versus Server.GetPointsSQL
The difference between the 2 methods lies in the query that the methods take in. You can find the
details of query string definition can be found in the PI SDK Programming Reference. To summarize
GetPointsSQL can implement complex queries, including joins with other PI tables. GetPoints cannot
do such complex queries, but it is faster, and can use custom point attributes, which GetPointsSQL
cannot.

Example
13

// Where server is a PISDK.Server object


// search condition is any tags with tagname that begins with 'sin'
PointList ptlist1 = server.GetPoints("tag='sin*'", null);
// GetPointsSQL allow us to search based on criteria other than the point
attributes
// like snapshot value of the tag is less than 80 (below)
NamedValues nvExtraTables = new NamedValues();
nvExtraTables.Add("picomp", "picomp");
PointList ptlist2 = server.GetPointsSQL("PIpoint.Tag = 'sin*' AND
PIcomp.Value < 80 AND PIpoint.Tag = PIcomp.Tag AND PIcomp.Time =
DATE(\"*\")", nvExtraTables, null);
//Accesing data from the PointList
foreach (PIPoint tmpPP in varPIPoints) {
//Now you can access the point via tmpPP
}
//You can also use index access to get to the points (one based)
varPIPoints[1].Name;

LISTDATA FUNCTIONS
Characteristics

Allows retrieving data on a list of PI Points rather than one at a time


Designed to improve performance by reducing the number of calls made from
the PI SDK to the PI Server

ListData.Snapshot

Description
Return a PointValues collection containing a single snapshot value for each point in the PointList.

EVENT PIPES
Characteristics

Designed to improve performance by getting notified of PI Server events (as


opposed to polling). The events the PI Server reports back include Permissions
Change, New Data events, Tag Configuration changes,

Description
Events were created to allow the application to react to things like a button press, a mouse click,
closing a form, moving a window, minimizing, etc. And for the PI Server that means that instead of
polling for a Tag for new data, the point will get back to you and tell you that there have been some
changes.
An EventPipe object contains items that have changed on the PI Server for its parent. When an
EventPipe property is retrieved through the parent it will return a unique event pipe that while kept
in scope will receive all the changed items for that parent. Multiple pipes on a single source will all
receive each event.

14

Different parents will contain different types of items but the interface to the event pipe remains the
same.

Parent Object
PIData

Changed Items
new and edit events

ListData

new and edit events

IPIData2 (PIData)

Archive data

ListData

Archive data

Example
Look for the vCampus Exclusive Webinar on EventPipes in the reference section.

MULTITHREADING APPLICATIONS
Characteristics

Designed to provide higher scalability through parallel/concurrent execution


Susceptible to thread-locks/race/starve conditions
Increases application complexity

Description
The following discussion of threading and PI SDK applies mainly to programs written in C#/C++
where it is possible to create multiple threads of execution. Typical Visual Basic programs will create
PI SDK objects in a single apartment and a single thread will service the PI SDK calls. Calls to the
objects are synchronized (sequential). This eliminates problems associated with cross-thread calls.
C#/C++ programmers that require multi-threaded access to the PI SDK in their programs should
consider this section. With the advent of VB.Net it is also now relatively easy to build multi-threaded
applications using Visual Basic.
The PI SDK library is an in-process server (DLL) that gets loaded on first access into a program's
process space. Calls to the PI SDK in a program are generally all made within the same process
(assuming you have not configured your machine to access the PI SDK through DCOM), but may be
made across threads. Most PI SDK objects in this release are built using the Apartment threading
model and as such, when created, are instantiated in Single Threaded Apartments (STA). Given the
object hierarchy used in the PI SDK model, almost all PI SDK child objects will end up being
instantiated in a single apartment, the one where the main PISDK object is first created. "Creatable"
objects, such as NamedValues and PITime, may be created in other apartments dictated by their
threading model and the type of apartment where the creation call is made. COM dictates that an
object in an STA can only execute on one thread (STAs have thread affinity). Calls from other threads
must be marshaled to the object's STA, which results in these other threads receiving a proxy to the
object rather than an actual interface pointer.
Any C++ thread calling COM objects must first call CoInitialize, CoInitializeEx, or OLEInitialize. This call
determines the apartment type for the thread. CoInitialize and OLEInitialize always uses an STA.
CoInitializeEx allows the caller to specify the apartment type. Subsequent calls to create objects in
the thread, will create objects in that apartment. Access to objects created in other apartments (by
other threads) must be handled through proxies.
15

An interesting question is how a thread initially obtains a proxy to an object in another apartment. It
is a violation of COM rules to pass raw object/interface pointers from one STA thread to another.
Doing so can create conditions where multiple threads are simultaneously executing code in the
same object. Some objects may be thread-safe and be able to handle this type of access, but as a
rule, apartment threaded objects are not required to be thread-safe because the COM rules and
conventions ensure calls to the object are serialized. To safely pass pointers between apartments
they need to be "marshaled." A thread (or process) receiving a marshaled interface actually receives
a proxy to the object. Discussion of marshalling techniques is beyond the scope of this document.
Refer to Microsoft documentation for details.
The PISDK object is designed as a per-thread single point of access to an object hierarchy. The object
is built as a "per thread singleton," meaning only one instance of the object is created in any thread.
Multiple requests to create the object within a thread return references to the original object. The
first code to request the object, (typically a call to CreateInstance, using "new" in VB.Net, or a smart
pointer constructor), results in the object being created in that thread's apartment. Subsequent calls
to create the PISDK object in other threads will return a PISDK object for that particular thread. The
hierarchies obtained from these separate PISDK objects are separate, though they communicate
with the same PI servers. This behavior is useful for writing worker threads that operate
independently of each other. Threads written this way avoid the performance cost of inter-thread
marshaling.
Note however that it is still perfectly acceptable to marshal a reference to a PISDK object (or its
children) across threads using standard COM calls. This allows designs where multiple threads share
the same COM objects. In this configuration, however, cross thread calls will be marshaled with the
non-creating thread receiving a proxy to the object's interface. For example, thread 1 creates a
PISDK object and retrieves a Server object from its Servers collection. It marshals this Server object
to thread 2. Thread 2 then uses the marshaled server pointer to obtain a PIPoints collection. The
PIPoints interface pointer in thread 2 in this case is a proxy. Subsequent calls on the proxy that
return interfaces to other objects (walking the hierarchy) will also return proxies. COM automatically
generates these proxies, returned through calls on another proxy.
There are some important implications of this behavior in the PI SDK. Accessing an object through a
proxy as discussed above involves cross-thread marshalling, which can have performance impacts.
When calling methods that require significant processing (for example cross platform calls to a PI
Server) the contribution of the proxy may be insignificant. For quick local access calls (for example
retrieving cached properties) the proxy call and marshalling may have a larger contribution to total
processing time.
When a thread terminates, its STA is removed, and all objects created in that STA are deleted. If
some other thread is holding a proxy to one of those objects, that proxy is not deleted, but will
return an RPC disconnection error on any attempt to use it. This can be a problem when the thread
creating the initial PISDK object terminates. All other threads trying to use the SDK will begin to
report disconnection errors. To prevent this problem, the thread that creates the initial PISDK object
should not terminate until all worker threads have already terminated. You can wait for a thread to
be terminated by calling WaitForSingleObject or one of its derivatives passing the thread handle, or
you can use other Win32 synchronization primitives.

16

In addition, the creating thread should keep the PISDK object alive by holding a reference-counted
pointer to that object, which is not released until just before the thread terminates. If you do not do
this, the PISDK object will delete itself when there are no outstanding references to it. This is not a
problem in itself, but if a worker thread subsequently tries to get a reference to the PISDK object, a
new instance of it will be created in that worker thread's STA. But the worker thread, unlike the main
thread, will not be waiting for all other threads to terminate, resulting in the problem described
above.
One final consideration when programming multi-threaded COM applications is that STAs (such as
those created by calling CoCreateInstance) need to provide a message loop to handle COM calls
from other threads. COM handles the serialization in a single-threaded apartment by posting
Windows messages to the apartment's thread. If the thread does not have an active message loop to
process the message, the call will block, freezing the application. If an object is to be marshaled, the
initial thread creating the object requires a message loop to handle requests for the proxy generated
by subsequent threads expecting to marshal the object. If you can be sure that none of the objects in
a particular apartment will be called from another thread, you need not have a message loop in that
apartment's thread. Introducing an active message loop into a thread requires breaking up the work
to be done by the thread into reasonable execution chunks so that messages continue to be serviced
in a reasonable time frame.
With version 1.3.1 of the PI SDK a Windows message pump was added to the synchronization
routines managing concurrent thread access to shared resources. There are implications of this
change:
If while processing a Windows message in your application, you call a PI SDK method, it is possible
for other Windows messages to be received by your application before your processing of the
original method is complete. This is due to a message pump in the PI SDK that is run to marshal PI
SDK calls on proxies from other threads onto the main PI SDK apartment thread (STA) where they
execute. In a sense, entering a PI SDK call while processing a Windows message makes your message
handler reentrant.

17

MULTI-THREADED NAMEDVALUE (MTNV) AND MULTI-THREADED


NAMEDVALUES (MTNVS )
In PI SDK 2010 R2, we have introduced new objects into the PI SDK object model MTNVS and MTNV.
MTNVS is a collection of MTNV objects. Each object in the collection has a name, which is a string,
and a value, which is a VARIANT. MTNVS is a thread-safe version of NamedValues. It implements all
the NamedValues and INamedValues2 methods and properties as well as native versions (prefaced
by "MT") of the same. Thread-safe objects like MTNVS helps to improve performance of your
multithreaded application because they can be instantiated in Multi-Threaded Apartment (MTA)
which means that it can access and receive direct calls from multiple threads without cross-thread
marshalling.
An example to illustrate this is the following console application:
namespace MTNVSPerformance
{
class Program
{
//[STAThread]
// insert this to make it STA - console apps
default to MTA
static void Main(string[] args)
{
const int NbrLoop = 50;
// number of loops
const int NbrItem = 100;
// number of items
Stopwatch st1 = new Stopwatch(); // StopWatch for NVS
Stopwatch st2 = new Stopwatch(); // StopWatch for MTNVS
//
// Test the performance of MTNVS versus NVS
//
try
{
//
// Test NVS performance
//
Console.WriteLine("*** Adding & Iterating NVS collection of "
+ NbrItem + " PIValues, looping " + NbrLoop + " times.");
st1.Start();
//
// Loop many times to make it easier to measure the total
//
for (int i = 0; i < NbrLoop; i++)
{
PISDKCommon.NamedValues nvs = new
PISDKCommon.NamedValues();
// create this here so we get a new one each loop
for (int j = 1; j <= NbrItem; j++)
{
// 1 based collection
string itemName = "Item" + j;
object itemVal = j;
nvs.Add(itemName, ref itemVal);
};
//
// Now iterate through each item
// Don't use foreach because we can't use it for MTNVS
//
for (int j = 1; j <= NbrItem; j++)
{
// 1 based collection
object objInx = j;
PISDKCommon.NamedValue curNV = nvs.get_Item(ref
objInx);
18

string curName;
object curVal;
curName = curNV.Name;
curVal = curNV.Value;
}
}
st1.Stop(); // StopWatch End
Console.WriteLine(">>> NVS Elapsed time: " +
st1.Elapsed.ToString());
//
// Test MTNVS performance
//
Console.WriteLine("*** Adding & Iterating MTNVS collection of
" + NbrItem + " PIValues, looping " + NbrLoop + " times.");
st2.Start();
//
// Loop many times to make it easier to measure the total
//
for (int i = 0; i < NbrLoop; i++)
{
PISDKCommon.MTNVS mtNVS = new PISDKCommon.MTNVS();
// create this here so we get a new one each loop
for (int j = 1; j <= NbrItem; j++)
{
// 1 based collection
string itemName = "Item" + j;
object itemVal = j;
mtNVS.MTAdd(itemName, ref itemVal);
};
//
// Now iterate through each item
//
for (int j = 1; j <= NbrItem; j++)
{
// 1 based collection
object objInx = j;
PISDKCommon.MTNV curMTNV = mtNVS.get_MTItem(ref
objInx);
string curName;
object curVal;
curName = curMTNV.MTName;
curVal = curMTNV.MTValue;
}
}
st2.Stop(); // StopWatch End
Console.WriteLine(">>> MTNVS Elapsed time: " +
st2.Elapsed.ToString());
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
//
// Report ratio NVS/MTNVS
//
double dblNVSTime = st1.Elapsed.Ticks;
double dblMTNVSTime = st2.Elapsed.Ticks;
double nvsMTNVSRatio = dblNVSTime / dblMTNVSTime;
string strRatio = String.Format("{0:F2}", nvsMTNVSRatio);
Console.WriteLine("Ratio of NVS to MTNVS = " + strRatio);
//
// Wait for keypress
19

//
Console.WriteLine("Press ENTER to continue...");
Console.ReadLine();
}
}
}

A C# console application like above defaults to using MTA and this program compares the speed of
looping through NamedValues with MTNVS. A quick comparison of running the program in MTA and
STA in one of our test environment gives something like

This result basically illustrates to us that using MTNVS in cases like this clearly offers significant
performance improvement compared to NamedValues.
Do take note that in programs which utilize STA like Windows Form applications, NamedValues will
still have better performance compared to MTNVS. We can see this by setting the program to use
STA by setting the [STAThread] attribute located at the start of the method, which will give the
following performance:

So you can consider the type of application that you are developing and choose which of these
options will give you better performance.
In PI SDK 2010 R2 or later, ValueAttributes property of PIValue object will be returned as MTNVS
collection instead of NamedValues collection in previous versions. This would help you improve the
performance of free-threaded applications that retrieve additional information related to the values
from the PI System (like Annotation and Substituted flag).

IPIVALUES2.GETVALUEARRAYS
IPIValues2 interface is a secondary interface supported by PIValues collection to include additional
features. One of which is the GetValuesArrays method is implemented to return all data in the
PIValues collection into parallel arrays, representing the value, timestamp and flags.
This is a more effective way to get values from PI Server when we are deploying PI SDK Interop
library in .NET framework. As PI SDK is a COM library, interop marshaling is involved when
parameters and return values is passed between the managed and unmanaged code, which means
additional overhead is incurred when we access objects across the interop boundary.
If we want to get timestamp and value of all archive values from a PIPoint for the last 2 hours using
PIData.RecordedValues call, we have to loop through all the PIValue objects in the returned
20

PIValues object. This would involve marshaling PIValue COM objects and incurring marshaling
overhead for every single PIValue object.
Using IPIValues2.GetValuesArrays method, we can bring the data returned as PIValues collection
across the interop boundary to managed code as arrays in 1 call, without incurring more overhead as
we access the values from the array.
A sample of using IPIValues2.GetValueArrays:
public long AddValuesToLB(PISDK.PIValues pivals, Int32 DisplayDig, ListBox
lstbox)
{
try
{
System.Array a1, a2, a3;
PISDK.IPIValues2 pivalarray = (PISDK.IPIValues2)pivals;
pivalarray.GetValueArrays(out a1, out a2, out a3);
object[] values = (object[])a1;
double[] timestamps = (double[])a2;
Int32[] pibits = (Int32[])a3;
PITimeServer.PITimeFormat pitm = new PITimeServer.PITimeFormatClass();
pitm.FormatString = "MM-dd hh:mm:ss.###";
for (int idx = 1; idx < values.Length; idx++)
{
pitm.UTCSeconds = timestamps[idx];
string strval = pitm.OutputString;
strval += " ";
strval += ValueToString(values[idx], DisplayDig);
if (pibits[idx] > 0)
{
if ((pibits[idx] &
(Int32)PISDK.PIValueFlagBitMask.eSubstitutedMask) ==
(Int32)PISDK.PIValueFlagBitMask.eSubstitutedMask)
strval += " S";
if ((pibits[idx] &
(Int32)PISDK.PIValueFlagBitMask.eAnnotatedMask) ==
(Int32)PISDK.PIValueFlagBitMask.eAnnotatedMask)
strval += " A";
if ((pibits[idx] &
(Int32)PISDK.PIValueFlagBitMask.eQuestionableMask) ==
(Int32)PISDK.PIValueFlagBitMask.eQuestionableMask)
strval += " Q";
}
lstbox.Items.Add(strval);
}
}
catch (Exception ex)
{
lstbox.Items.Add(ex.Message);
return 1;
}
return 0;
}
private string ValueToString(object varVal, Int32 DisplayDig)
{
string strtemp;
if (varVal.GetType().IsCOMObject)
// translate the COM object and get the string representation
else if (varVal.GetType().Name == "Double")
21

{
// Could use DisplayDigits...
strtemp = varVal.ToString();
}
else
strtemp = varVal.ToString();
return strtemp;
}

22

CONCLUSION
PI SDK applications can often provide the highest performance and scalability for accessing PI data,
but this requires careful planning and development using the above techniques. As a developer
working with PI SDK, it is also recommended to use the newer versions PI SDK as there are new
features added as well as changes under the hood that can help you improve the performance of
your PI SDK application, like the changes implemented with the introduction of MTNVS and MTNV in
PI SDK 2010 R2.
For most users, it may be more efficient to leave these issues to OSIsoft and use PI Web Services
instead. PI Web Services leverages all of these PI SDK optimizations and provides a simple
query/response interface for programmers, allowing them to focus on what data to read/write to PI,
instead of how to do it.
The efficiency and ease of development with PI Web Services may be more cost-effective than doing
raw PI SDK programming, especially when coding in .NET languages or creating modern web-based
applications. The OSIsoft vCampus development license (and the corresponding PI System Access
runtime license) allows customers to choose the right tool for the job, so please consider which
approach (PI SDK, PI Web Services, or another PI Data Access tool) makes the most sense for your
particular situation, and for the long run.

23

RESOURCES
Find Application Bottlenecks with Visual Studio Profiler - http://msdn.microsoft.com/enus/magazine/cc337887.aspx
MSDN Delegate help page because there is more to the delegates that what was showed here
http://msdn.microsoft.com/en-us/library/900fyy8e%28v=VS.100%29.aspx
MSDN Event help page, it is always useful to have a reference around
http://msdn.microsoft.com/en-us/library/8627sbea%28v=VS.100%29.aspx
Optimizing your PI SDK applications vCampus Webinar
http://vcampus.osisoft.com/auditorium/m/webinars/4554.aspx
Event Pipes vCampus Webinar - http://vcampus.osisoft.com/media/p/2109.aspx
Understanding and Using COM Threading Models http://msdn.microsoft.com/enus/library/ms809971.aspx
Description and Workings of OLE Threading Models http://support.microsoft.com/kb/q150777/
Single-Threaded Apartments http://msdn.microsoft.com/enus/library/ms6801112@28VS.85%29.aspx
PI SDK Help %ProgramFiles%\PIPC\HELP\pisdk.chm

For the list of calls that currently support true asynchronous behavior: PI SDK
Documentation \ PI-SDK Programming reference \ PIAsynchStatus \ PIAsynchStatus
Object

Interop Marshalling http://msdn.microsoft.com/en-us/library/eaw10et3.aspx

24

REVISIONS
01-Oct-2010

Initial version by Cristobal Escamilla

05-Oct-2011

Updated version by Han Yong Lee to include section on MTNV and MTNVS

19-Oct-2011

Reviewed by Jay Lakumb

15-Dec-2011

Updated by Han Yong Lee on PIData.PlotValues as the call supports asynchronous


call now. Added more description on what the call does.

16-Dec-2011

Reviewed by Harri Talvala

22-Dec-2011

Reviewed by Ray Verhoeff

22-Dec-2011

Updated by Han Yong Lee on IPIValues2.GetValueArrays and GetPoints versus


GetPointsSQL

25

Das könnte Ihnen auch gefallen