I am developing a C# custom OPC Client, I started off writing in a console app for quickness, everything works perfectly as I want it to.
Then I decided to make a windows form application for a visual experience.
The windows form application just simply stops working, stops reading data from the OPC server after around a minute. Where as the console app keeps reading and reading.
I can't find anything obvious in debug mode either.
I am absolutely clutching at straws here and hopefully someone could shed some light.
Each application is using .dll files provided by OPCFoundation.
Here is the console application
static void Main(string[] args)
{
Opc.URL url = new Opc.URL("opcda://localhost/RSLinx OPC Server");
Opc.Da.Server server = null;
OpcCom.Factory fact = new OpcCom.Factory();
server = new Opc.Da.Server(fact, null);
server.Connect(url, new Opc.ConnectData(new System.Net.NetworkCredential()));
// Create a group
Opc.Da.Subscription group;
Opc.Da.SubscriptionState groupState = new Opc.Da.SubscriptionState();
groupState.Name = "Group";
groupState.Active = true;
group = (Opc.Da.Subscription)server.CreateSubscription(groupState);
// add items to the group.
Opc.Da.Item[] items = new Opc.Da.Item[6];
items[0] = new Opc.Da.Item();
items[0].ItemName = "[UX1]F20:9";
items[1] = new Opc.Da.Item();
items[1].ItemName = "[UX1]F22:30";
items[2] = new Opc.Da.Item();
items[2].ItemName = "[UX1]F22:6";
items[3] = new Opc.Da.Item();
items[3].ItemName = "[UX1]F18:8";
items[4] = new Opc.Da.Item();
items[4].ItemName = "[UX1]F22:32";
items[5] = new Opc.Da.Item();
items[5].ItemName = "[UX1]F22:5";
items = group.AddItems(items);
group.DataChanged += new Opc.Da.DataChangedEventHandler(OnTransactionCompleted);
}
static void OnTransactionCompleted(object group, object hReq, Opc.Da.ItemValueResult[] items)
{
Console.WriteLine("------------------->");
Console.WriteLine("DataChanged ...");
for (int i = 0; i < items.GetLength(0); i++)
{
Console.WriteLine("Item DataChange - ItemId: {0}", items[i].ItemName);
Console.WriteLine(" Value: {0,-20}", items[i].Value);
Console.WriteLine(" TimeStamp: {0:00}:{1:00}:{2:00}.{3:000}",
items[i].Timestamp.Hour,
items[i].Timestamp.Minute,
items[i].Timestamp.Second,
items[i].Timestamp.Millisecond);
}
Console.WriteLine("-------------------<");
}
Here is the WinForm application
public Form1()
{
InitializeComponent();
_Form1 = this;
}
public static Form1 _Form1;
public void update(string message)
{
this.richTextBox1.Text = message;
}
private void Form1_Load(object sender, EventArgs e)
{
readplc();
}
static void readplc()
{
Opc.URL url = new Opc.URL("opcda://localhost/RSLinx OPC Server");
Opc.Da.Server server = null;
OpcCom.Factory fact = new OpcCom.Factory();
server = new Opc.Da.Server(fact, null);
server.Connect(url, new Opc.ConnectData(new System.Net.NetworkCredential()));
// Create a group
Opc.Da.Subscription group;
Opc.Da.SubscriptionState groupState = new Opc.Da.SubscriptionState();
groupState.Name = "Group";
groupState.Active = true;
group = (Opc.Da.Subscription)server.CreateSubscription(groupState);
// add items to the group.
Opc.Da.Item[] items = new Opc.Da.Item[6];
items[0] = new Opc.Da.Item();
items[0].ItemName = "[UX1]F20:9";
items[1] = new Opc.Da.Item();
items[1].ItemName = "[UX1]F22:30";
items[2] = new Opc.Da.Item();
items[2].ItemName = "[UX1]F22:6";
items[3] = new Opc.Da.Item();
items[3].ItemName = "[UX1]F18:8";
items[4] = new Opc.Da.Item();
items[4].ItemName = "[UX1]F22:32";
items[5] = new Opc.Da.Item();
items[5].ItemName = "[UX1]F22:5";
items = group.AddItems(items);
group.DataChanged += new Opc.Da.DataChangedEventHandler(OnTransactionCompleted);
}
static void OnTransactionCompleted(object group, object hReq, Opc.Da.ItemValueResult[] items)
{
for (int i = 0; i < items.GetLength(0); i++)
{
UIUpdater TEXT = new UIUpdater();
TEXT.UpdateText(items.GetLength(0).ToString() + " t " + i.ToString() + "Item DataChange - ItemId:" + items[i].ItemName +
"Value: " + items[i].Value + " TimeStamp: " + items[i].Timestamp.Hour + ":" +
items[i].Timestamp.Minute + ":" + items[i].Timestamp.Second + ":" + items[i].Timestamp.Millisecond);
}
}
UIUpdate Class
class UIUpdater
{
public void UpdateText(string DATA)
{
Form1._Form1.update(DATA);
}
public class UpdateUI
{
public int updatedRows { get; set; }
public string Custom1 { get; set; }
public string Custom2 { get; set; }
public string Custom3 { get; set; }
public string exception { get; set; }
public plcTextStatus PLCStatus { get; set; }
}
Any questions please ask!
As suspected, this is a cross-threading issue. The problem is that you can't update the UI from any other thread than the UI thread. The event for the transaction completed actually gets called on a separate thread, so its updating the UI.
It works for a little while because its relatively tolerant of errors, however you are probably reaching a point that you are deadlocking or throwing an exception that isn't getting caught (or reported).
The fix is simple enough though.
In this method:
public void update(string message)
{
this.richTextBox1.Text = message;
}
Change it to:
public void update(string message)
{
richTextBox1.Invoke(
(MethodInvoker) delegate
{
richTextBox1.Text = message;
});
}
What this does is tells the richTextBox1
to "invoke" or run the following delegate (function) on its owning thread (aka, the UI thread).
You should really try to avoid using static
methods and references in this code. I don't see any reason that the code you have shouldn't be instance methods instead of static ones.
Just as a side note, I write OPC programs that deal with thousands of tags and hundreds of UI updates per second. What you are doing works for small demo programs but won't scale up very well. When the architecture grows, you need to start batching your UI updates so you aren't busy calling the UI thread repeatedly inside of an update.
Edit
Another problem you have is using local references (to the OPC server, and subscription for example) and demonstrates a memory leak and zombie object. What is happening is that the readplc
method goes out of scope and you've created references to objects inside that are being held in memory. Since you have no way to unsubscribe the event, the event keeps firing. These variables should be declared outside the scope of the readplc
method so you can properly unsubscribe the event and shutdown the OPC server. Otherwise you are leaving zombie subscriptions (look at the RSLinx OPC Diagnostics page, you'll see all your subscriptions sitting there).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With