0

I'm using a Background worker to read values in and to pass values to Worker_ProgressChanged, to update UI.

In Worker_DoWork:

while (agi.DvmReadyToRead)   // wait for digipot to be adjusted before reading in worker
{
    Thread.Sleep(20);
    Application.DoEvents();
    //logS.Debug("Waiting for ready to read in worker");
}
Thread.Sleep(40);  // Give digipot chance to make the change
agi.SendSoftwareTriggerOne();
Thread.Sleep(7);    // Duration for above command to execute
A = agi.ReadOne();
Thread.Sleep(1);    
agi.InitOne();
Thread.Sleep(1);    
sAndH3 = A[0];
worker.ReportProgress(0, new System.Tuple<double>(sAndH3));
agi.DvmReadyToRead = true;

In Worker_ProgressChanged:

while (!agi.DvmReadyToRead)
{
    //logS.Debug("waiting for ready to read in progress");
    Thread.Sleep(0);
    Thread.Sleep(0);
    Thread.Sleep(0);
    Thread.Sleep(0);
    Thread.Sleep(0);
    Application.DoEvents();  // Exception thown here
    Thread.Sleep(1);     // wait for DVM reading
}
agi.DvmReadyToRead = false;

// Then goes on to adjust output voltage up or down

This is working fine the first time round using

Application.DoEvents();

however after first run, I get a stackoverflow at this point. After reading many posts on here DoEvents is not the best way of doing what I am trying to achieve. So what I would like is a way to pass a Boolean back to DoWork, or another way to allow worker to be able to read the agi.DvmReadyToRead Boolean.

Thanks!

chasher
  • 149
  • 11
  • 1
    This looks like a quite broken attempt to solve a problem. But I'm not sure what your overall problem is - all you're showing us currently is your attempt to solve it. Beyond saying I'm certain it's not correct, I'm not sure what advice to give - because I'm not even sure a `BackgroundWorker` is going to be part of the right solution but that's all you've really shown us. – Damien_The_Unbeliever Oct 02 '18 at 14:09
  • @Damien_The_Unbeliever Thanks for comments. 1) Without the DoEvents in ProgressChanged, DoWork cannot see the state of agi.DvmReadyToRead, so doesn't measure the voltage and return it to Progress changed. 2) With DoEvents it causes exception. 3) What is better than DoEvents to "free up" worker. 4) Is there a way to pass this agi.DvmReadyToRead Boolean back to the worker than a global variable. Please? – chasher Oct 02 '18 at 14:17
  • 1
    DoEvents is very, very evil. It simple way to blow up your program with a SOE is by it allowing your ProgressChanged event handler to run again, even before the previous invocation is complete. Which calls DoEvents again, which allows ProgressChanged to get called again, etcetera. You'll have to learn to live without it, it is never necessary. – Hans Passant Oct 02 '18 at 14:26
  • @HansPassant Thanks, but what can I use instead of DoEvents to "free up" worker please? – chasher Oct 02 '18 at 14:31
  • My crystal ball is pretty cloudy, but it whispers that this is a single-threaded COM component. They can't operate correctly on a worker thread. A simple workaround is to use a Timer (the one from the toolbox) and poll the component. A practical Interval value is 15, can't go faster than that. Which is okay, you are already Sleep() longer than that. Or you need to create a non-worker thread that is suitable for such a component, it must call Application.Run(). An example [is here](https://stackoverflow.com/a/21684059/17034). Go for the timer first. – Hans Passant Oct 02 '18 at 14:47
  • @HansPassant Thanks, your crystal ball maybe cloudy, but it's had a lot more experience than me! The multimeter that I am communicating with is an COM interop, but this is working ok, it's only the agi.DvmReadyToRead bool that is being set in progress changed and not changing state in the worker. agi.DvmReadyToRead is a property in another class. Before your comment, I was just trying out lock as this seemed what I needed to do, but to no avail. I shall read your example and try the timer out. Thanks! – chasher Oct 02 '18 at 15:21
  • @HansPassant two years ago when this question was asked I didn't want to say anything (then or now) that might risk coming across in an unintended way. But I remember this day so well! You are such a legendary and venerable figure and I've learned _so much_ from the many thousands of answers you've given. When I managed to post an answer and it edged out a comment made by you, it _really meant_ something to me, like maybe I could be someone someday LOL! Today I've revisiting this just to say thanks for being such an inspiration for so many of us. – IVSoftware Jun 06 '20 at 19:45

1 Answers1

0

If I understand your question, you are describing a very common pattern in Test and Measurement where you have an instrument that takes some time after triggering it before it gets a reading. But you want to know when the reading happens so that you can take some action (like update a ProgressBar or TextBox perhaps) and you want be able to cancel the worker loop.

When I need to do this myself, I like to use the System.Threading.Tasks to simplify things. I'll post a complete pattern here in the hope that you can find something of use to solve the issue you are having.

To be clear, I am trying to answer your question of "a way to pass a Boolean back to DoWork..." by saying that one way to do this is to fire an Event from Worker_DoWork that can contain Boolean (like you asked) or double (in my example) or any other information you choose.

Good luck!

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

namespace StackOverflow02
{
    public partial class DVMLoopRunner : Form
    {
        public DVMLoopRunner()
        {
            InitializeComponent();
            DVMReadingAvailable += Form1_DVMReadingAvailable;
            ContinueOrCancel += Form1_ContinueOrCancel;
        }

        // See if User has turned off the Run button then cancel worker
        private void Form1_ContinueOrCancel(Object sender, CancelEventArgs e)
        {
            e.Cancel = !checkBoxRunMeterLoop.Checked;
        }

        // The DVM, after being triggered + some delay, has come up with a new reading.
        private void Form1_DVMReadingAvailable(Object sender, DVMReadingAvailableEventArgs e)
        {
            // To update GUI from worker thread requires Invoke to prevent Cross-Thread Exception
            Invoke((MethodInvoker)delegate
            {
                textBox1.Text = e.Reading.ToString("F4");
            });
        }

        // Make our events so that we can be notified of things that occur
        public event CancelEventHandler ContinueOrCancel;                   
        public event DVMReadingAvailableEventHandler DVMReadingAvailable;

        // This is how we will provide info to the GUI about the new reading
        public delegate void DVMReadingAvailableEventHandler(Object sender, DVMReadingAvailableEventArgs e);
        public class DVMReadingAvailableEventArgs : EventArgs
        {
            public readonly double Reading;
            public DVMReadingAvailableEventArgs(double reading)
            {
                Reading = reading;
            }
        }

        // When the User checks the box, Run the worker loop
        private void checkBoxRunMeterLoop_CheckedChanged(Object sender, EventArgs e)
        {
            if(checkBoxRunMeterLoop.Checked)
            {
                Task.Run(() => ReadDVMWorker());
            }
        }

        // Worker Loop
        private void ReadDVMWorker()
        {
            while(true)
            {
                CancelEventArgs e = new CancelEventArgs();
                ContinueOrCancel?.Invoke(this, e);
                if (e.Cancel) return;               // If User has turned off the Run button then stop worker
                ReadDVM();                          // This worker thread will block on this. So trigger, wait, etc.
            }
        }

        // DVM Takes some period of time after trigger
        void ReadDVM()
        {
            Thread.Sleep(1000);
            double newSimulatedReading = 4.5 + Random.NextDouble();
            DVMReadingAvailable?.Invoke(this, new DVMReadingAvailableEventArgs(newSimulatedReading));
        }
        Random Random = new Random();   // Generate random readings for simulation
    }
}

enter image description here

IVSoftware
  • 5,732
  • 2
  • 12
  • 23
  • Thank you very much for you answer, your crystal ball must have been less cloudy! I have built and tested your example, now I am going to try to implement this in my application! Thanks especially for taking the time to write an example program, I hope in the future my knowledge/experience will develop so I can too help others! – chasher Oct 03 '18 at 08:41
  • Glad I could help! Let me know if you hit any bumps in the road. – IVSoftware Oct 03 '18 at 12:51
  • I have now adapted it into a new class with my code and it works great! – chasher Oct 04 '18 at 07:56
  • Nice! Thanks for letting me know. – IVSoftware Oct 06 '18 at 08:18
  • If you've come to this spot _also_ check out my more-recent instrument related threading-and-polling answers [use ping in infinite loop](https://stackoverflow.com/a/62288635/5438626) and [background validation](https://stackoverflow.com/a/62251543/5438626). – IVSoftware Jun 09 '20 at 19:09