Инициализируйте объект внутри потока и управляйте им из основного потока - PullRequest
0 голосов
/ 11 января 2019

Я создал объект, который используется для управления испытательным оборудованием (осциллографом), который общается с помощью библиотеки Visa. Этот объект (объект области видимости) работает нормально, но есть один метод, который я создал, чтобы запросить область для усреднения формы сигнала, выполнение которого занимает некоторое время (около секунды или около того), и блокирует выполнение пользовательского интерфейса во время получения данные.

Чтобы обойти эту проблему, я сначала попытался создать объект задачи и использовать этот объект задачи для выполнения функции, которая запрашивает область действия для данных; но я обнаружил, что что-то в самом объекте драйвера Visa, по-видимому, все еще выполняется в главном потоке (и, следовательно, замедляет мой пользовательский интерфейс).

Затем я сделал еще один тест и создал новый поток, и этот поток вызвал функцию. Внутри этой функции я инициализировал объект области действия, установил рабочие параметры, а затем вызвал долгосрочную функцию. На этот раз мой пользовательский интерфейс работал как обычно, без замедлений.

Итак, похоже, мне нужно на самом деле инициализировать объект области внутри нового потока, чтобы он действительно работал асинхронно. Но теперь у меня есть новый вызов. Мне нужно получить доступ к свойствам и методам объектов из основного потока, чтобы настроить вещи, запросить информацию о состоянии и т. Д. Есть ли чистый способ эффективно читать и записывать свойства и методы класса из другого потока? Или есть какие-нибудь библиотеки, чтобы сделать это проще?

Моя текущая идея - создать класс-оболочку для объекта области, а затем этот класс-оболочку инициализировать объект области в новом потоке. Но я не уверен в лучшем способе эффективного доступа к членам объекта. Или еще лучше, есть ли лучший подход к этой проблеме?

РЕДАКТИРОВАТЬ: Ниже приведена дополнительная информация и код для тестовой программы, которую я написал. Пользовательский интерфейс представляет собой простую форму с кнопками Acquire и Connect и двумя метками (одна для отображения измерений, а другая показывает число, которое увеличивается при нажатии кнопки «Нажать»:

UI for test app

Вот код созданного мною объекта Scope:

using System;
using Ivi.Scope.Interop;
using Tektronix.Tkdpo2k3k4k.Interop;
using System.Diagnostics;

namespace Test_App
{
    public class DPO4034
    {
        #region [NOTES] Installing TekVisa Drivers for DPO4034
        /*
        1. Download and install the TekVisa Connectivity Software from here:
           https://www.tek.com/oscilloscope/tds7054-software/tekvisa-connectivity-software-v411

        2. Check under Start -> All Programs -> TekVisa and see if the "Open Choice Installation Manager" shortcut works.If not, then update all shortcuts to point to the correct base folder for the TekVISA files, which is "C:\Program Files\IVI Foundation\VISA\".

        3. Download the DPO4000 series IVI driver from here:
           https://www.tek.com/oscilloscope/dpo4054-software/dpo2000-dpo3000-dpo4000-ivi-driver

        4. After running the unzip utility, open the unzipped folder and goto x64 -> Shared Components, and run the IviCleanupUtility_2.0.0.exe utility to make sure no shared IVI components exist.

        5. Run the IviSharedComponents64_2.1.1.exe file to install shared components.

        6. Go up one folder and open the IVI Driver Folder and run the Tkdpo2k3k4k-x64.msi installer to install the scope IVI driver.

        7. In the VS project, add references to the following COM components:
            • IviDriverLib
            • IviScopeLib
            • Tkdpo2k3k4kLib

        8. Right Click on each of the three references in the Solution Explorer and select Properties in the menu. When the properties window appears, set the "Embed Interop Types" property to False.
        */
        #endregion

        #region Class Variables

        Tkdpo2k3k4kClass driver;    // IVI Driver representing the DPO4034
        IIviScope scope;            // IVI Scope object representing the DPO4034

        #endregion

        #region Class Constructors

        public DPO4034()
        {
            this.driver = new Tkdpo2k3k4kClass();
            this.scope = (IIviScope)driver;
        }

        ~DPO4034()
        {
            this.Disconnect();
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Returns true if the scope is connected (initialized)
        /// </summary>
        public bool Connected
        {
            get
            {
                return this.driver.IIviDriver_Initialized;
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Initializes the connection to the scope
        /// <paramref name="reset"/>Resets the scope after connecting if set to true</param>
        /// </summary>
        /// <returns>True if the function succeeds</returns>
        public bool Connect(bool reset = false)
        {
            try
            {
                if (!this.Connected)
                {
                    this.Disconnect();
                }

                this.driver.Initialize("TCPIP::10.10.0.200::INSTR", true, reset, "Simulate=false, DriverSetup= Model=DPO4034");
                return true;
            }
            catch (Exception ex)
            {
                PrintError(ex, "Connect");
                return false;
            }
        }

        /// <summary>
        /// Closes the connection to the scope
        /// </summary>
        /// <returns>True if the function succeeds</returns>
        public bool Disconnect()
        {
            try
            {
                if (this.Connected)
                { 
                    this.driver.Close();
                }
                return true;
            }
            catch (Exception ex)
            {
                PrintError(ex, "Disconnect");
                return false;
            }
        }

        /// <summary>
        /// Reads the average value of the waveform on the selected channel
        /// </summary>
        /// <param name="channel">1-4 for channels 1 to 4</param>
        /// <returns>The measured average value</returns>
        public double ReadWaveformAverage(int channel)
        {
            if (this.Connected)
            {
                try
                {
                    double value = 0;
                    this.scope.Measurements.Item["CH" + channel.ToString()].FetchWaveformMeasurement(IviScopeMeasurementEnum.IviScopeMeasurementVoltageAverage, ref value);
                    return value;
                }
                catch (Exception ex)
                {
                    PrintError(ex, "ReadWaveformAverage");
                    return 0;
                }
            }
            else
            {
                PrintError("Oscilloscope not connected", "ReadWaveformAverage");
                return 0;
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Prints an error message to the debug console
        /// </summary>
        /// <param name="err">Error object</param>
        /// <param name="source">Source of the error</param>
        private void PrintError(Exception err, string source = "") //, bool showMessageBox = false)
        {
            Debug.Print($"Error: {err.Message}");
            Debug.Print($"Source: {source}");
        }

        /// <summary>
        /// Prints an error message to the debug console
        /// </summary>
        /// <param name="err">Error object</param>
        /// <param name="source">Source of the error</param>
        private void PrintError(string error, string source = "")
        {
            Debug.Print($"Error: {error}");
            Debug.Print($"Source: {source}");
        }

        #endregion
    }
}

Вот код для версии формы, которая использует асинхронную функцию и задачи для непосредственного вызова функций получения:

using System;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Test_App
{
    public partial class Form1 : Form
    {
        byte number = 0;

        public Form1()
        {
            InitializeComponent();
        }

        private void cmdAcquire_Click(object sender, EventArgs e)
        {
            takeMeasurements();
        }

        async void takeMeasurements()
        {
            try
            {
                // Create new instance of the scope object and connect to it
                DPO4034 Scope = new DPO4034();
                Scope.Connect();

                // Update status
                PrintStatus(Scope.Connected ? "Connected" : "Error");

                // Loop continuously and print the samples to the status label
                while (Scope.Connected)
                {
                    double inputVoltage = await Task.Run(() => Scope.ReadWaveformAverage(1));
                    double inputCurrent = await Task.Run(() => Scope.ReadWaveformAverage(2));
                    double outputVoltage = await Task.Run(() => Scope.ReadWaveformAverage(3));

                    PrintStatus($"CH1: {inputVoltage}\n" +
                                $"CH2: {inputCurrent}\n" +
                                $"CH3: {outputVoltage}\n");
                }
            }
            catch (Exception)
            {
                PrintStatus("Error");
            }
        }

        private void cmdIncrement(object sender, EventArgs e)
        {
            // This is just something for me to make the interface do to see
            // how responsive it is
            lblNumber.Text = number.ToString();
            number++;
        }

        // Prints status text to the label on the form
        private void PrintStatus(string text)
        {
            Status.Text = text;
        }
    }
}

и вот код для версии формы, которая использует отдельный поток для запуска объекта области действия:

using System;
using System.Threading;
using System.Windows.Forms;

namespace Test_App
{
    public partial class Form1 : Form
    {
        Thread t;
        byte number = 0;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            t = new Thread(new ThreadStart(takeMeasurements));
        }

        private void cmdAcquire_Click(object sender, EventArgs e)
        {
            t.Start();
        }

        // Function to create scope object and take acquisitions
        void takeMeasurements()
        {
            try
            {
                // Create new instance of the scope object and connect to it
                DPO4034 Scope = new DPO4034();
                Scope.Connect();

                // Update status
                PrintStatus(Scope.Connected ? "Connected" : "Error");

                // Loop continuously and print the samples to the status label
                while (Scope.Connected)
                {
                    double inputVoltage = Scope.ReadWaveformAverage(1);
                    double inputCurrent = Scope.ReadWaveformAverage(2);
                    double outputVoltage = Scope.ReadWaveformAverage(3);

                    PrintStatus($"CH1: {inputVoltage}\n" +
                                $"CH2: {inputCurrent}\n" +
                                $"CH3: {outputVoltage}\n");
                }
            }
            catch (Exception)
            {
                PrintStatus("Error");
            }
        }

        private void cmdIncrement(object sender, EventArgs e)
        {
            // This is just something for me to make the interface do to see
            // how responsive it is
            lblNumber.Text = number.ToString();
            number++;
        }

        // Prints status text to the label on the form
        private void PrintStatus(string text)
        {
            if (!this.IsDisposed)
            {
                this.BeginInvoke((MethodInvoker)delegate
                {
                    Status.Text = text;
                });
            }
            else
            {
                t.Abort();
            }
        }
    }
}

Надеюсь, это даст больше понимания того, чего я пытаюсь достичь. Спасибо всем за ваши комментарии, и я с нетерпением жду ваших отзывов.

EDIT2: Просто для большей ясности, метод, который я предпочел бы использовать (если это возможно), это метод, использующий задачи. В текущей программе объект Scope инициализируется в верхней части формы в главном потоке и доступен для нескольких объектов в программе.

1 Ответ

0 голосов
/ 17 января 2019

Для всех, кто заинтересовался, я наконец-то нашел решение проблемы, возникающей при зависании графического интерфейса при выполнении функции ReadWaveformData ().

Ответ заключался в создании нового потока внутри класса Scope, который вызывал бы функцию инициализации для инициализации внутренней области и объектов драйвера. Тогда поток ничего не будет делать, кроме как сидеть и размещать экземпляры до тех пор, пока внутри задачи не будет вызвана функция ReadWavveformData (). Вот модифицированный класс DPO4034:

using System;
using Ivi.Scope.Interop;
using Tektronix.Tkdpo2k3k4k.Interop;
using System.Diagnostics;
using System.Threading;

namespace Test_App
{
    public class DPO4034
    {
        #region [NOTES] Installing TekVisa Drivers for DPO4034
        /*
        1. Download and install the TekVisa Connectivity Software from here:
           https://www.tek.com/oscilloscope/tds7054-software/tekvisa-connectivity-software-v411

        2. Check under Start -> All Programs -> TekVisa and see if the "Open Choice Installation Manager" shortcut works.If not, then update all shortcuts to point to the correct base folder for the TekVISA files, which is "C:\Program Files\IVI Foundation\VISA\".

        3. Download the DPO4000 series IVI driver from here:
           https://www.tek.com/oscilloscope/dpo4054-software/dpo2000-dpo3000-dpo4000-ivi-driver

        4. After running the unzip utility, open the unzipped folder and goto x64 -> Shared Components, and run the IviCleanupUtility_2.0.0.exe utility to make sure no shared IVI components exist.

        5. Run the IviSharedComponents64_2.1.1.exe file to install shared components.

        6. Go up one folder and open the IVI Driver Folder and run the Tkdpo2k3k4k-x64.msi installer to install the scope IVI driver.

        7. In the VS project, add references to the following COM components:
            • IviDriverLib
            • IviScopeLib
            • Tkdpo2k3k4kLib

        8. Right Click on each of the three references in the Solution Explorer and select Properties in the menu. When the properties window appears, set the "Embed Interop Types" property to False.
        */
        #endregion

        #region Class Variables

        Tkdpo2k3k4kClass driver;    // IVI Driver representing the DPO4034
        IIviScope scope;            // IVI Scope object representing the DPO4034
        Thread t;                   // Thread to initialize the scope objects in to ensure that they async method calls do not run on the main thread

        #endregion

        #region Class Constructors

        public DPO4034()
        {
            t = new Thread(new ThreadStart(Initialize));
            t.Start();

            // Wait for scope object to be initialized in the thread
            while (this.scope == null);
        }

        ~DPO4034()
        {
            this.Disconnect();
            t.Abort();
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Returns true if the scope is connected (initialized)
        /// </summary>
        public bool Connected
        {
            get
            {
                return this.driver.IIviDriver_Initialized;
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Initializes the connection to the scope
        /// <paramref name="reset"/>Resets the scope after connecting if set to true</param>
        /// </summary>
        /// <returns>True if the function succeeds</returns>
        public bool Connect(bool reset = false)
        {
            try
            {
                if (!this.Connected)
                {
                    this.Disconnect();
                }

                this.driver.Initialize("TCPIP::10.10.0.200::INSTR", true, reset, "Simulate=false, DriverSetup= Model=DPO4034");
                return true;
            }
            catch (Exception ex)
            {
                PrintError(ex, "Connect");
                return false;
            }
        }

        /// <summary>
        /// Closes the connection to the scope
        /// </summary>
        /// <returns>True if the function succeeds</returns>
        public bool Disconnect()
        {
            try
            {
                if (this.Connected)
                { 
                    this.driver.Close();
                }
                return true;
            }
            catch (Exception ex)
            {
                PrintError(ex, "Disconnect");
                return false;
            }
        }

        /// <summary>
        /// Reads the average value of the waveform on the selected channel
        /// </summary>
        /// <param name="channel">1-4 for channels 1 to 4</param>
        /// <returns>The measured average value</returns>
        public double ReadWaveformAverage(int channel)
        {
            if (this.Connected)
            {
                try
                {
                    double value = 0;
                    this.scope.Measurements.Item["CH" + channel.ToString()].FetchWaveformMeasurement(IviScopeMeasurementEnum.IviScopeMeasurementVoltageAverage, ref value);
                    return value;
                }
                catch (Exception ex)
                {
                    PrintError(ex, "ReadWaveformAverage");
                    return 0;
                }
            }
            else
            {
                PrintError("Oscilloscope not connected", "ReadWaveformAverage");
                return 0;
            }
        }

        #endregion

        #region Private Methods

        private void Initialize()
        {
            this.driver = new Tkdpo2k3k4kClass();
            this.scope = (IIviScope)driver;

            // Does nothing but allow the objects to exist on the separate thread
            while (true)
            {
                Thread.Sleep(int.MaxValue);
            }
        }

        /// <summary>
        /// Prints an error message to the debug console
        /// </summary>
        /// <param name="err">Error object</param>
        /// <param name="source">Source of the error</param>
        private void PrintError(Exception err, string source = "") //, bool showMessageBox = false)
        {
            Debug.Print($"Error: {err.Message}");
            Debug.Print($"Source: {source}");
        }

        /// <summary>
        /// Prints an error message to the debug console
        /// </summary>
        /// <param name="err">Error object</param>
        /// <param name="source">Source of the error</param>
        private void PrintError(string error, string source = "")
        {
            Debug.Print($"Error: {error}");
            Debug.Print($"Source: {source}");
        }

        #endregion
    }
}

Если это связано с версией TestApp, которая использует асинхронные задачи для выполнения функции ReadWaveformData (), то все идет гладко, и мне не нужно полностью переписывать класс области видимости, чтобы он работал в моем программа. Надеюсь, что это полезно для всех, кто может столкнуться с подобным испытанием.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...