首页 > 解决方案 > 在线程内初始化一个对象并从主线程控制它

问题描述

我创建了一个对象,用于控制使用 Visa 库进行通信的测试设备(示波器)。这个对象(范围对象)工作正常,但是我创建了一种方法来查询范围以获取波形平均值,该方法需要一段时间才能执行(大约一秒左右),并且在获取数据。

为了解决这个问题,我最初尝试创建一个任务对象并使用这个任务对象来执行查询数据范围的函数;但我发现 Visa 驱动程序对象本身中的某些内容显然仍在主线程上执行(从而减慢了我的 UI)。

然后我又做了一次测试并创建了一个新线程,并让这个线程调用一个函数。在这个函数内部,我初始化了作用域对象,设置了运行参数,然后调用了长时间运行的函数。这一次,我的 UI 像往常一样反应灵敏,没有减速。

所以现在,我似乎需要在一个新线程中实际初始化范围对象,以使其真正异步运行。但现在我有一个新的挑战。我需要从主线程访问对象属性和方法以进行设置、查询状态信息等。有没有一种干净的方法可以有效地从另一个线程读取和写入类的属性和方法?或者是否有任何现有的库可以使这更简单?

我目前的想法是为范围对象创建一个包装类,然后让这个包装类在新线程中初始化范围对象。但我不确定有效访问对象成员的最佳方式。或者更好的是,有没有更好的方法来解决这个问题?

编辑:下面是我编写的测试程序的更多信息和代码。UI 只是一个简单的表单,带有 Acquire 和 Connect 按钮,以及两个标签(一个用于显示测量结果,另一个显示一个数字,当我单击“Click”按钮时该数字会增加:

测试应用的 UI

这是我创建的 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对象在主线程的窗体顶部被初始化,并被程序内的多个对象访问。

标签: c#multithreadingobjectparallel-processingasync-await

解决方案


对于任何感兴趣的人,我终于找到了解决我在执行 ReadWaveformData() 函数时遇到的 GUI 冻结问题的解决方案。

答案是在 Scope 类中创建一个新线程,该线程将调用 Initialization 函数来初始化内部范围和驱动程序对象。然后线程什么也不做,只是坐下来托管实例,直到在任务中调用 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
    }
}

如果这与使用异步任务执行 ReadWaveformData() 函数的 TestApp 版本配对,那么事情运行顺利,我不需要完全重写范围类来让它在我的程序中工作。希望这对可能遇到类似挑战的其他人有所帮助。


推荐阅读