none
ActiveX STA и Windows.Forms RRS feed

  • Вопрос

  • Добрый день.

    В приложении Windows.Forms используется ActiveX-контрол стороннего производителя для получения по сети видеопотока и его отображения. Там присутствует метод Connect(), который, при невозможности подключения подвешивает на несколько секунд UI приложения. Я попытался с помощью создания делегата и вызова BeginInvoke() сделать вызов данного метода асинхронно. Однако это не помогло. Почитал ветку форума и понял, что проблема кроется в STA-модели используемого ActiveX. Т.е. несмотря на то, что вызовы делаются из другого потока, выполняться они всё равно будут в том потоке, где был создан ActiveX.

    Подскажите, есть ли решение для устранение подвисания UI при вызове длительных операций на ActiveX c STA-моделью.

    Спасибо.

Ответы

  • Ну и форму создайте ему в отдельном потоке, но пока он будет коннектиться она будет тормозить. ))
    • Помечено в качестве ответа tulosba 16 мая 2012 г. 10:36
    Отвечающий

Все ответы

  • Добрый день.

    А кто вам мешает и создавать контрол в отдельном потоке?

    Отвечающий
  • А кто вам мешает и создавать контрол в отдельном потоке?

    Так ведь надо полученные данные отображать на форме.
  • Так ведь надо полученные данные отображать на форме.

    А кто вам мешает получив данные для их вывода обратно переключаться в поток диспетчера формы?

    Отвечающий
  • А кто вам мешает получив данные для их вывода обратно переключаться в поток диспетчера формы?
    Не совсем понял вопроса. Не могли бы Вы пояснить на примере или хотя бы сказать какими функциями это должно быть обеспечено?
  • Есть WinForms приложение, в нем есть форма. На форме Label с именем label1. Также у формы, есть поле int i. Я хочу раз в пол секунды увеличивать i на 1 и выводить значение в label1. Будем считать, что создание и инициализация таймера занимает много времени и я хочу их выполнять в отдельном потоке. Для этого, я могу написать вот такой код:

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
    
            // Создание потока и его запуск
            Thread th = new Thread(new ThreadStart(CreateTimer));
            th.Start();
        }
    
        int i = 0;
    
        private void CreateTimer()
        {
            // Метод который будет запущен в отдельном потоке
            System.Timers.Timer t = new System.Timers.Timer();
            t.Interval = 500;
            t.Elapsed += t_Tick;
            t.Start();
        }
    
        void t_Tick(object sender, EventArgs e)
        {
            // Событие таймера
            ChangeTextInLabel();
        }
    
        private void ChangeTextInLabel()
        {
            // Собственно работа
            i++;
            label1.Text = i.ToString();
        }
    }

    Если запустить приложение в таком виде, то при попытке из таймера созданного в другом потоке изменить label1, будет ошибка:

    Чтобы избежать этой ошибки и надо осуществить переключение в поток диспетчера формы. Делается это легко. Нам нужно только изменить наш метод ChangeTextInLabel:

     

    private void ChangeTextInLabel()
    {
        if (this.InvokeRequired) // Проверка. что запущено не из того потока
        {
            this.BeginInvoke((Action)ChangeTextInLabel); // Запускам из потока формы
        }
        else
        {
            // Собственно работа
            i++;
            label1.Text = i.ToString();
        }
    }

     

    Отвечающий
  • Спасибо за столь подробное сообщение, но дело всё же несколько в другом. Вы предлагали создать контрол в другом потоке, а потом переключиться для отображения на основной поток. Т.е. на базе Вашего примера это, наверное должно было выглядеть как создание label1 в потоке, отличном от GUI-потока, а потом отображение label1 уже на форме. С подходом к обращению к контролам, созданным на форме, из других потоков, я немного знаком (как раз то, что Вы описали в примере). Суть же моей проблемы сводится к тому, что несмотря на вызов метода из другого потока, фактически его обработка производится в UI-потоке, что приводит к подвисанию UI. Посмотрите пожалуйста ссылку на форум, которую я привел в первом сообщении, это должно прояснить ситуацию. Приведу вырезку из моего кода:
            public void Connect()
            {
                foreach (AxVPORTSDKLib.AxVPortSDK ax in aXs)
                {
                    //ax.Connect();
                    ConnectOneAsync(ax);
                }
            }
    
            private void ConnectOne(AxVPORTSDKLib.AxVPortSDK ax)
            {
                ax.Connect();
                //Thread.Sleep(5000);
            }
    
            private void ConnectOneAsync(AxVPORTSDKLib.AxVPortSDK ax)
            {
                ConnectOneDelegate caller = new ConnectOneDelegate(ConnectOne);
                caller.BeginInvoke(ax, new AsyncCallback(EndConnectOneCallback), null);
            }
    
            void EndConnectOneCallback(IAsyncResult ar)
            {
                AsyncResult result = (AsyncResult)ar;
                ConnectOneDelegate caller = (ConnectOneDelegate)result.AsyncDelegate;
                caller.EndInvoke(ar);
            }
    
            private delegate void ConnectOneDelegate(AxVPORTSDKLib.AxVPortSDK ax);

    Суть в том, что в методе
    private void ConnectOne(AxVPORTSDKLib.AxVPortSDK ax)
            {
                ax.Connect();
                //Thread.Sleep(5000);
            }
    вызов ax.Connect() выполняется в UI-потоке. А если заменить его на
    Thread.Sleep(5000);
    то это ожидание уже выполняется на пуле потоков, т.е. без подвешивания UI.
  • Создавайте объект который вы обзываете ax не в конструкторе формы (или где вы там его создаете), а в отдельном потоке, так как я создаю Timer.

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

    Для инициализации контрола нужно установить свойство Parent, т.е. по сути форму, на которой он будет рисоваться. Не совсем понимаю, как это реализовать, чтобы не возникло исключение "Cross-thread operation not valid" при получении адреса родительского контрола.
  • Ну и форму создайте ему в отдельном потоке, но пока он будет коннектиться она будет тормозить. ))
    • Помечено в качестве ответа tulosba 16 мая 2012 г. 10:36
    Отвечающий
  • Ну и форму создайте ему в отдельном потоке, но пока он будет коннектиться она будет тормозить. ))
    То есть, по сути получается нужно в новом потоке вызвать Application.Run() на свежесозданной форме. Так?
  • Зачем. Приложение у вас уже запущено. В отдельном потоке создаете форму и показываете ее. Форма, как минимум показывается и на действия пользователя реагирует:

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
    
        private void button1_Click(object sender, EventArgs e)
        {
            Thread th = new Thread(new ThreadStart(ShowForm));
            th.Start();
        }
    
        Form2 f = null;
        AutoResetEvent fClose = new AutoResetEvent(false);
    
        private void ShowForm()
        {
            f = new Form2();
            f.Show();
            while (f != null && f.Visible)
            {
                Application.DoEvents();
            }
        }
    }

    Отвечающий
  • Только вот если

    f.Show();
    while (f != null && f.Visible)
    {
        Application.DoEvents();
    }
    заменить на
    Application.Run( f );
    процессору станет легче дышать


  • Оптимизацией примера не занимался. Думаю там можно сделать кучу вариантов... Вы свою первоначальную проблему то, решили?

    Отвечающий
  • Вы свою первоначальную проблему то, решили?

    Полного подвисания UI конечно удалось избежать. Но форма с ActiveX по-прежнему подтормаживает. Таким образом проблему можно считать частично решенной. Т.е. я готов пометить Ваш комментарий как ответ, но дальнейшее обсуждение было бы интересным. Т.к. для ясности хотелось бы знать можно ли обеспечить работу STA ActiveX без замораживания интерфейса.
  • Почитайте что ни будь про потоки. У вас есть поток, в котором идет обработка интерфейса через очередь событий. Если в этом потоке, кто то начинает крутить цикл, в рамках которого выполнение идет достаточно значительное время, то из очереди событий ничего не выбирается и соответственно идет "подвисание" интерфейса. Избавиться от подвисания можно только переписав тот комопнент, который вместо асинхронного выполнения операции выполняет его синхронно в потоке обработчика событий.
    Отвечающий
  • То, что Вы говорите - вещи очевидные. Неожиданным для меня была передача выполнения длительной функции ActiveX в UI-поток, несмотря на попытки вызвать ее асинхронно. Таким образом, скорее читать надо про COM Apartments и их реализацию, а не о потоках как таковых.