2015-05-15 18 views
10

Ich entwickle einen C# benutzerdefinierten OPC Client, ich begann in einer Konsolen App für Schnelligkeit zu schreiben, alles funktioniert perfekt, wie ich es will.C# OPC Anwendungen Identischer Code, aber anders arbeiten

Dann entschied ich mich, eine Windows-Formular-Anwendung für eine visuelle Erfahrung zu machen.

Die Windows-Formularanwendung hört einfach auf zu arbeiten, stoppt nach etwa einer Minute das Lesen von Daten vom OPC-Server. Während die Konsolen-App liest und liest.

Ich kann auch im Debug-Modus nichts Offensichtliches finden.

Ich bin absolut auf Strohhalme hier und hoffentlich könnte jemand etwas Licht werfen.

Jede Anwendung verwendet DLL-Dateien, die von OPCFoundation bereitgestellt werden. Hier

ist die Konsolenanwendung

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("-------------------<"); 
     } 

Hier wird die WinForm Anwendung

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 Klasse

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; } 


     } 

Bei Fragen wenden Sie sich bitte!

+0

Was ist der 'UIUpdater'? –

+0

Nur eine Klasse, um das UserInterface Thread zu aktualisieren – SK2017

+0

Kannst du den Code dafür schreiben? Dies sieht wie ein Cross-Threading-Problem aus. Ich habe viele Anwendungen mit OPC selbst entwickelt, RSLinx ist ziemlich robust, sieht aus wie Sie eine Verbindung zu einem Micrologix oder SLC, oder etwas aus der PLC5-Ära ... –

Antwort

5

Wie vermutet, ist dies ein Cross-Threading-Problem. Das Problem besteht darin, dass Sie die Benutzeroberfläche nicht von einem anderen als dem UI-Thread aktualisieren können. Das Ereignis für die abgeschlossene Transaktion wird tatsächlich in einem separaten Thread aufgerufen, sodass die UI aktualisiert wird.

Es funktioniert für eine Weile, weil es relativ tolerant gegenüber Fehlern ist, jedoch erreichen Sie wahrscheinlich einen Punkt, an dem Sie sich verklemmen oder eine Ausnahme werfen, die nicht erwischt (oder gemeldet) wird.

Die Lösung ist jedoch einfach genug.

Bei dieser Methode:

public void update(string message) 
{ 
    this.richTextBox1.Text = message; 
} 

ändern Sie es an:

public void update(string message) 
{ 
    richTextBox1.Invoke(
     (MethodInvoker) delegate 
     { 
      richTextBox1.Text = message; 
     }); 
} 

Was das bedeutet ist, erzählt die richTextBox1 zu "aufrufen" oder die folgend Delegierten (Funktion) auf seinem Besitz Thread ausgeführt (aka, der UI-Thread).

Sie sollten wirklich versuchen, static Methoden und Referenzen in diesem Code zu vermeiden. Ich sehe keinen Grund, dass der Code, den Sie haben, keine Instanzmethoden anstelle von statischen Methoden sein sollte.

Nur als eine Randnotiz, ich schreibe OPC-Programme, die mit Tausenden von Tags und Hunderte von UI-Updates pro Sekunde. Was Sie tun, funktioniert für kleine Demo-Programme, wird aber nicht sehr gut skalieren. Wenn die Architektur wächst, müssen Sie die UI-Aktualisierungen im Batch-Verfahren starten, damit Sie den UI-Thread nicht wiederholt innerhalb eines Updates aufrufen.

bearbeiten

Ein weiteres Problem, das Sie lokale Referenzen haben verwendet (zum OPC-Server und Abonnement zum Beispiel) und zeigt ein Speicherleck und Zombie-Objekt. Was passiert, ist, dass die readplc-Methode den Gültigkeitsbereich verlässt und Sie Verweise auf Objekte im Speicher erstellt haben, die sich im Speicher befinden. Da Sie die Veranstaltung nicht abbestellen können, wird das Ereignis weiterhin ausgelöst. Diese Variablen sollten außerhalb des Gültigkeitsbereichs der Methode readplc deklariert werden, damit Sie das Ereignis ordnungsgemäß abbestellen und den OPC-Server herunterfahren können. Andernfalls verlassen Sie Zombie-Abonnements (sehen Sie sich die RSLinx OPC Diagnostics-Seite an, dort werden alle Ihre Abonnements angezeigt).

+0

Ich habe den Code nur auf "Invoke" geändert, es hat immer noch keinen Unterschied gemacht, er stürzte nach so langer Zeit immer noch ab – SK2017

0

Setzen Sie Ihren Server außerhalb der replic() -Methode als Formularebenenobjekt. Solange Ihr Formular instanziiert (nicht geschlossen) ist - Ihr Server-Objekt ist aktiv und Ihr Abonnement-Ereignis sollte ebenfalls aktiv sein.

Der Server wird höchstwahrscheinlich vom Garbage Collector erfasst.

Opc.Da.Server server = null; 

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(); 
      **this.server = new Opc.Da.Server(fact, null);** 
    .... 
    }