title: Initialize an object inside a thread and control it from main thread
body: <p>I have created an object which is used to control a piece of test equipment (oscilloscope) which communicates using the Visa library. This object (scope object) works fine, but there is one method I created to query the scope for a waveform average, which takes a while to execute (around a second or so), and blocks execution of the UI while it is acquiring the data.</p>
  
  <p>To get around this problem, I initially tried creating a task object and using this task object to execute the function that is querying the scope for the data; but I found that something in the Visa driver object itself was apparently still executing on the main thread (and thus slowing down my UI).</p>
  
  <p>I then did one more test and created a new thread, and had this thread call a function. Inside this function, I initialized the scope object, setup the operating parameters, and then called the long-running function. This time, my UI was as responive as normal with no slowdowns.</p>
  
  <p>So now, It seems that I need to actually initialize the scope object inside a new thread in order to get it to truly run asynchronously. But now I have a new challenge. I need to access the objects properties and methods from the main thread to set things up, query status info, etc. Is there a clean way to effectively read and write to a class's properties and methods from another thread? Or are there any existing libraries available to make this simpler?</p>
  
  <p>My current idea is to create a wrapper class for the scope object and then have this wrapper class initialize the scope object in a new thread. But I'm not sure of the best way to access the object's members efficiently. Or better yet, is there a better approach to this problem?</p>
  
  <p><strong>EDIT:</strong> Below is some more information and code for the test program I wrote. The UI is a just a simple form with Acquire and Connect buttons, and two labels (one for showing the measurements and the other shows a number that gets incremented as I click on the "Click" button:</p>
  
  <p><a href="https://i.stack.imgur.com/0AQIY.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/0AQIY.png" alt="UI for test app"></a></p>
  
  <p>Here's the code for the Scope Object I created:</p>
  
  <pre><code>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 -&gt; All Programs -&gt; 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 -&gt; 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
  
          /// &lt;summary&gt;
          /// Returns true if the scope is connected (initialized)
          /// &lt;/summary&gt;
          public bool Connected
          {
              get
              {
                  return this.driver.IIviDriver_Initialized;
              }
          }
  
          #endregion
  
          #region Public Methods
  
          /// &lt;summary&gt;
          /// Initializes the connection to the scope
          /// &lt;paramref name="reset"/&gt;Resets the scope after connecting if set to true&lt;/param&gt;
          /// &lt;/summary&gt;
          /// &lt;returns&gt;True if the function succeeds&lt;/returns&gt;
          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;
              }
          }
  
          /// &lt;summary&gt;
          /// Closes the connection to the scope
          /// &lt;/summary&gt;
          /// &lt;returns&gt;True if the function succeeds&lt;/returns&gt;
          public bool Disconnect()
          {
              try
              {
                  if (this.Connected)
                  { 
                      this.driver.Close();
                  }
                  return true;
              }
              catch (Exception ex)
              {
                  PrintError(ex, "Disconnect");
                  return false;
              }
          }
  
          /// &lt;summary&gt;
          /// Reads the average value of the waveform on the selected channel
          /// &lt;/summary&gt;
          /// &lt;param name="channel"&gt;1-4 for channels 1 to 4&lt;/param&gt;
          /// &lt;returns&gt;The measured average value&lt;/returns&gt;
          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
  
          /// &lt;summary&gt;
          /// Prints an error message to the debug console
          /// &lt;/summary&gt;
          /// &lt;param name="err"&gt;Error object&lt;/param&gt;
          /// &lt;param name="source"&gt;Source of the error&lt;/param&gt;
          private void PrintError(Exception err, string source = "") //, bool showMessageBox = false)
          {
              Debug.Print($"Error: {err.Message}");
              Debug.Print($"Source: {source}");
          }
  
          /// &lt;summary&gt;
          /// Prints an error message to the debug console
          /// &lt;/summary&gt;
          /// &lt;param name="err"&gt;Error object&lt;/param&gt;
          /// &lt;param name="source"&gt;Source of the error&lt;/param&gt;
          private void PrintError(string error, string source = "")
          {
              Debug.Print($"Error: {error}");
              Debug.Print($"Source: {source}");
          }
  
          #endregion
      }
  }
  </code></pre>
  
  <p>Here's the code for the version of the form that uses an async function and tasks to directly call the acquisition functions:</p>
  
  <pre><code>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(() =&gt; Scope.ReadWaveformAverage(1));
                      double inputCurrent = await Task.Run(() =&gt; Scope.ReadWaveformAverage(2));
                      double outputVoltage = await Task.Run(() =&gt; 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;
          }
      }
  }
  </code></pre>
  
  <p>and here's the code for the version of the form that uses a separate thread to run the scope object:</p>
  
  <pre><code>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();
              }
          }
      }
  }
  </code></pre>
  
  <p>I hope this gives some more insight into what I'm trying to accomplish. Thank you all for your comments and I look forward to your feedback.</p>
  
  <p><strong>EDIT2:</strong> Just to be more clear, the method I would prefer to use (if possible) is the one using tasks. In the current program, the Scope object is initialized at the top of the form on the main thread and accessed by multiple objects within the program.</p>