С# OPC Приложения Идентичный код, но работают по-разному

Я разрабатываю пользовательский OPC-клиент С#, я начал писать в консольном приложении быстро, все работает отлично, как я хочу.

Затем я решил сделать приложение в форме окна для визуального восприятия.

Приложение в виде окон просто перестает работать, перестает читать данные с сервера OPC примерно через минуту. Где, когда консольное приложение продолжает чтение и чтение.

Я не могу найти ничего очевидного в режиме отладки.

Я абсолютно хватаюсь за соломинку здесь, и, надеюсь, кто-то может пролить некоторый свет.

Каждое приложение использует файлы .dll, предоставляемые OPCFoundation.

Вот консольное приложение

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

Вот приложение 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

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


        }

Любые вопросы, пожалуйста, спросите!

Ответ 1

Как и предполагалось, это проблема с перекрестной резьбой. Проблема в том, что вы не можете обновлять пользовательский интерфейс из любого другого потока, кроме потока пользовательского интерфейса. Событие для выполненной транзакции фактически вызывается в отдельном потоке, поэтому его обновление пользовательского интерфейса.

Он работает некоторое время, потому что он относительно терпим к ошибкам, однако вы, вероятно, достигли точки, в которой вы зашли в тупик или выбросили исключение, которое не попадает (или не сообщается).

Исправление достаточно простое.

В этом методе:

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

Измените его на:

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

То, что это делает, сообщает richTextBox1 "вызывать" или запускать следующий делегат (функция) в свой собственный поток (ака, поток пользовательского интерфейса).

Вы действительно должны стараться избегать использования методов и ссылок static в этом коде. Я не вижу причин, по которым код, который у вас есть, не должен быть экземпляром вместо статических.

Как примечание, я пишу программы OPC, которые обрабатывают тысячи тегов и сотни обновлений пользовательского интерфейса в секунду. То, что вы делаете, работает для небольших демонстрационных программ, но не будет масштабироваться очень хорошо. Когда архитектура растет, вам нужно начать пакетную установку обновлений пользовательского интерфейса, чтобы вы не занимались многократным вызовом потока пользовательского интерфейса внутри обновления.

Edit

Другая проблема заключается в использовании локальных ссылок (например, на OPC-сервере и подписке) и демонстрирует утечку памяти и объект зомби. Случается, что метод readplc выходит за пределы области видимости, и вы создали ссылки на объекты внутри, которые хранятся в памяти. Поскольку у вас нет возможности отказаться от подписки на мероприятие, мероприятие продолжает стрелять. Эти переменные должны быть объявлены вне области действия метода readplc, чтобы вы могли правильно отменить подписку на событие и завершить работу OPC-сервера. В противном случае вы покидаете подписки на зомби (посмотрите страницу диагностики RSLinx OPC Diagnostics, вы увидите все ваши подписки).

Ответ 2

Поместите ваш сервер снаружи метода readplc() в качестве объекта уровня формы. Пока ваша форма будет создана (не закрыта) - ваш объект сервера будет активным, и так должно быть событие вашей подписки.

Сервер, скорее всего, собирается сборщиком мусора.

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