2015-05-15 18 views
10

Sto sviluppando un client OPC personalizzato C#, ho iniziato a scrivere in un'app console per velocità, tutto funziona perfettamente come voglio.C# OPC Applicazioni Codice identico, ma funziona in modo diverso

Quindi ho deciso di creare un'applicazione Windows per un'esperienza visiva.

L'applicazione di Windows form semplicemente smette di funzionare, interrompe la lettura dei dati dal server OPC dopo circa un minuto. Dove l'app della console continua a leggere e leggere.

Non riesco a trovare nulla di ovvio in modalità di debug.

Qui sono assolutamente aggrappato a cannucce e spero che qualcuno possa far luce.

Ogni applicazione utilizza i file dll forniti da OPCFoundation.

Ecco l'applicazione console

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

Ecco l'applicazione WinForm

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 Classe

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


     } 

Qualsiasi domanda chieda!

+0

Che cos'è l'UIUpdater? –

+0

Solo una classe per aggiornare il thread UserInterface – SK2017

+0

Puoi pubblicare il codice per questo? Questo sembra un problema di cross-threading. Ho sviluppato molte applicazioni usando OPC da solo, RSLinx è piuttosto robusto, sembra che tu ti stia connettendo a un Micrologix o SLC, o qualcosa dell'era PLC5 ... –

risposta

5

Come sospetto, si tratta di un problema di cross-threading. Il problema è che non puoi aggiornare l'interfaccia utente da nessun altro thread rispetto al thread dell'interfaccia utente. L'evento per la transazione completata viene effettivamente chiamato su un thread separato, quindi viene aggiornato l'interfaccia utente.

Funziona per un po 'di tempo perché è relativamente tollerante agli errori, tuttavia probabilmente stai raggiungendo un punto in cui ti trovi in ​​una situazione di stallo o un'eccezione che non viene rilevata (o segnalata).

La correzione è abbastanza semplice però.

In questo metodo:

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

modificarla in:

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

Quello che fa è racconta la richTextBox1 a "richiamare" o eseguire il seguente delegato (funzione) sul suo filo possedere (aka, il thread dell'interfaccia utente).

Si dovrebbe davvero cercare di evitare l'uso di metodi static e riferimenti in questo codice. Non vedo alcun motivo per cui il codice che hai non dovrebbe essere un metodo di istanza invece di quelli statici.

Proprio come una nota a margine, scrivo programmi OPC che gestiscono migliaia di tag e centinaia di aggiornamenti dell'interfaccia utente al secondo. Quello che stai facendo funziona per piccoli programmi demo ma non si scalerà molto bene. Quando l'architettura cresce, è necessario avviare il batching degli aggiornamenti dell'interfaccia utente in modo da non essere più impegnati a richiamare ripetutamente il thread dell'interfaccia utente all'interno di un aggiornamento.

Modifica

Un altro problema che hai sta usando riferimenti locali (per il server OPC, e l'abbonamento per esempio) e dimostra una perdita di memoria e oggetto di zombie. Quello che sta succedendo è che il metodo readplc esce dal campo di applicazione e hai creato riferimenti agli oggetti interni che vengono tenuti in memoria. Poiché non hai modo di annullare l'iscrizione all'evento, l'evento continua a sparare. Queste variabili devono essere dichiarate al di fuori dell'ambito del metodo readplc in modo da poter annullare correttamente l'iscrizione all'evento e arrestare il server OPC. Altrimenti lascerai gli abbonamenti zombie (guarda la pagina RSLinx OPC Diagnostics, vedrai tutti i tuoi abbonamenti seduti lì).

+0

Ho appena cambiato il codice in Invoke, ancora non ha fatto la differenza, è comunque andato in crash dopo tanto tempo – SK2017

0

Mettere il server all'esterno del metodo readplc() come oggetto a livello di modulo. Fintanto che il tuo modulo è istanziato (non chiuso) - il tuo oggetto server sarà attivo e così dovrebbe essere il tuo evento di iscrizione.

Il server viene probabilmente raccolto dal Garbage Collector.

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);** 
    .... 
    } 
Problemi correlati