Robert Joseph Dacunto
Robert Joseph Dacunto

Reputation: 523

New to threading, need help making a static variable thread safe with background worker

As the title suggests, I'm new to the concept of threading and I'm learning as I go. I'm working on a program as part of a research project involving a 3D printer. I have two pieces of hardware I need to interface with: a small stage that moves up and down, and a force sensor that can detect when an object on the stage makes contact with the sensor which acts as the signal to stop moving the stage. As I said, I'm new to the whole threading concept, so I'm not even sure if I'm designing this correctly, if I should use backgroundworker or the Thread class, how to ensure thread-safety, etc.

I"m writing the program in C# using Windows Forms and decided to use two background worker controls in Visual Studio/.NET Framework, one to monitor the force sensor reading and the other to control the stage moving up and down.

It has to be fast and accurate; the stage needs to stop moving the moment a pre-defined sensor value is detected. I have two static classes, a ZStageControl static class and a ForceSensorControl static class.

ForceSensorControl has a method called UpdateSensor() which returns the current value of the force sensor, and the ZStageControl has a private static bool variable, forceDetected (set to false by default), a public property ForceDetected that gets/sets this variable, and a method called GoodToMove. GoodToMove is defined as follows:

    public static bool GoodToMove()
    {
            if (forceDetected == true ||
                Position() < MinHeight ||
                Position() > MaxHeight)
                return false;
            else
                return true;
    }

My sensor update backgroundworker dowork code is defined as follows:

    private void sensorUpdateBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;

        double currentForceValue;

        while (ForceSensorControl.IsConnected() == true)
        {
            if (worker.CancellationPending == true)
            {
                e.Cancel = true;
                break;
            }
            else
            {
                try
                {
                    currentForceValue = Double.Parse(ForceSensorControl.UpdateSensor());
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);

                    disconnectForceSensor();

                    return;
                }

                if (currentForceValue >= forceSensorStopValue)
                {
                    if (this.ForceDetected != null)
                    {
                        this.ForceDetected(this, 
                            new ForceDetectedEventArgs(ZStageControl.Position(), currentForceValue));
                    }
                }
                else if (ZStageControl.ForceDetected == true)
                    ZStageControl.ForceDetected = false;

                forceSensorValueLabel.Text = currentForceValue.ToString() + "g";
            }
        }
    }

So as long as the sensor remains connected, continuously loop and update the force sensor vlaue. If it detects the proper force value, it fires the ForceDetected event. If it reads the value as less than the force sensor stop value, but ForceDetected is still set to true, it simply sets to false (since when the force is detected, it will stop the stage from moving and then return it to its default position, so it should reset the force detected variable).

The event code is defined as follows:

    public void FormMain_ForceDetected(object sender, ForceDetectedEventArgs e)
    {
        if (ZStageControl.ForceDetected == true)
            return;

        ZStageControl.ForceDetected = true;

        feedbackRichTextBox.Text += "\nForce Detected at position " +
            e.ForceDetectedPosition.ToString() + " with a value of " +
            e.ForceDetectedValue.ToString() + "\n";

        ScrollToEndOfFeedbackBox(feedbackRichTextBox);

        soundPlayer.Play();
    }

The thread to move the Z-Stage up or down is defined as follows:

    private void zStageMoveBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;

        ZStageMoveDirections direction = (ZStageMoveDirections)e.Argument;

        while (ZStageControl.IsConnected == true)
        {
            if (worker.CancellationPending == true)
            {
                e.Cancel = true;
                break;
            }
            else
            {
                System.Threading.Thread.Sleep(zStageSpeed);

                if (direction == ZStageMoveDirections.Down)
                {
                    ZStageControl.MoveDown(true);
                }
                if (direction == ZStageMoveDirections.Up)
                {
                    ZStageControl.MoveUp(true);
                }

                zStagePositionUpdateLabel.Text = ZStageControl.Position().ToString();
            }
        }
    }

The code that calls the DoWork event for the Z-Stage move is controlled by an if statement that checks if ZStageControl.GoodToMove() is true. So while GoodToMove() returns true, the Z-Stage thread can fire.

The issue I'm having is that I'm not sure if I'm designing this right, if I'm using the backgroundworker properly, and I know my variables are not thread-safe because at certain points GoodToMove() returns true and other times it returns false, even though there is clearly no force being detected. It seems to have a mind of its own. I just know nothing about thread-safety. Should I simply use the THread class instead of the background worker, is there a certain way to ensure the forceDetected variable/GoodToMove() method operates properly across these threads?

Upvotes: 0

Views: 339

Answers (1)

Matthew
Matthew

Reputation: 10444

I think your approach is inherently flawed.

You seem to be designing a system with a constantly looping monitor and then a check of that monitor when you want to execute a "move."

This is inherently problematic because you've created a race condition between your safety check operation and your move operation.

Consider this:

1       // check if move is ok -- PASS
(async) // move suddenly becomes not ok!
2       // move is executed

Instead you should think about this problem as entirely synchronous (as validation checks and executions should be entirely atomic). Whenever a move is requested you should check if it's permitted and then decide whether or not to execute.

1 // exclusive lock
2 // check if move is ok -- PASS
3 // execute move
4 // release lock

Upvotes: 2

Related Questions