ReelThymeOS
ReelThymeOS

Reputation: 35

Best way to pass data between threads when using BackgroundWorker class

I have a Winforms app that I'm developing. There is a time-consuming task that the user will initiate by pressing a button. I don't want my UI to freeze and I also want information to continuously come back from that task that can then be used to update UI controls and a progress bar.

I researched and it looks like the BackgroundWorker class is intended for these types of use cases. However, I looked at many examples and it's unclear to me how to pass data from my worker thread back to my UI thread. Most examples simply focus on passing back a percentage complete number via the ReportProgress() method, and the ProgressChanged event handler is where we are told to interact with the UI.

Here is what I've done so far:

  1. Created a background worker reference via the components toolbox called "backgroundWorker1"
  2. Created a button-click event handler for the UI button control
  3. Inside the event handler I call backgroundWorker1.RunWorkerAsync();
  4. Implemented my time-consuming task inside the backgroundWorker1_DoWork() method.

The literature says not to try to interact with the UI inside the DoWork() method, but rather via the ProgressChanged event handler. But how do I pass the data to that event handler so that it can update the UI controls?

Any insight is appreciated. Here is a sample of the code, some of it is pseudo-code.

My worker method:

private async Task<bool> runAttenRoutineAsync()
    {
        bool success = false;
        double measurement = 0.0;

        for (int i = 0; i < STEPS_MAX; i++)
        {
            double setPointLow = (i * 0.5) - 0.1;
            double setPointHigh = (i * 0.5) + 0.1;

            do
            {
                    //Acquire measurement from equipment
                    // success = someFunction(out measurement);
                    Thread.Sleep(100);
                }
                while (measurement < setPointLow || measurement > setPointHigh);
            }

            update_controls_sliders(someGlobalInt);
            label1.Text = ((double)(someGlobalInt) / 20).ToString("0.00");

        }

        return success;
    }

Main (UI) thread:

private async void button6_Click(object sender, EventArgs e)
    {
        //various functions to set up test equipment

        await workerRoutineAsync();

        return;
    }

Upvotes: 0

Views: 78

Answers (1)

flackoverstow
flackoverstow

Reputation: 726

You could send some complex user state along with the progress:


        
        private Dictionary<int, string> _backgroundSteps = new Dictionary<int, string>()
        {
            [1] = "Preparing frobbles for {0}",
            [2] = "Executing wibbles for {0}",
            [3] = "Cleaning up {0} frobbles and {1} wibbles"
        };

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            //prepare frobbles
            var database = new DB();
            var preparingFor = database.GetUser();

            backgroundWorker1.ReportProgress(1, new string[] { preparingFor });

            PrepareFrobbles();

            backgroundWorker1.ReportProgress(2, new string[] { preparingFor });

            ExecuteWibbles();

            backgroundWorker1.ReportProgress(3, new string[] { database.CountFrobbles().ToString(), database.CountWibbles().ToString() });

            CleanUp();
        }


        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {

            _statusLabel.Text = string.Format(_backgroundSteps[e.ProgressPercentage], (string[])e.UserState);
        }

Here I'm using the progress to just count a 3 step process, passing some varying state along with the message, and simply updating a label with a message.. I could get a lot more involved in the ProgressChanged, using conditonal logic to do different things depending on the int passed, making better use of the state object etc..

Or I could not even use the overload of ReportProgress that takes a state, and just report the int, cycling a highlight through some panel of labels showing what stage the process was at etc.. It's quite open as to how you use this. If you were deleting 100,000 database records, you're free to pass the count as the percentage progress and have a ProgressChanged handler like statusLabel.Text = "Deleted "+e.ProgressPercentage+" records" - it won't explode if you pass numbers outside the range 0 to 100 as the percentage


These days we'd probably use Task Async Pattern instead, and have our method that does work look like this:


    private async Task DoSomeLongWork(){

        statusLabel.Text = "Preparing frobbles";

        await PrepareFrobblesAsync();

        statusLabel.Text = "Executing wibbles";

        await ExecuteWibblesAsync();

        statusLabel.Text = "Executing wibbles";

        await CleanUpAsync();

    }

The code executing this is the UI thread if you havent put any other multithreading into your app, so it's safe for it to set the text of the label.

The UI thread doesn't get tied up doing the long work of preparing the frobbles and executing the wibbles, which would cause your app UI to freeze; that work is handed off and while the task that controls it is being awaited the UI thread disappears off back to the place it came from to get on with its normal job of drawing the UI.

When the frobbles are finished being prepared (for example) and the task is done, the UI thread resumes executing of this code from just after the await PrepareFrobblesAsync, sets another status, then disappears off again while it awaits the execution of the wibbles etc

Upvotes: 1

Related Questions