Link to home
Start Free TrialLog in
Avatar of David L. Hansen
David L. HansenFlag for United States of America

asked on

Backgroundworker is doing side-work correctly but not updating the progressbar.

The following code has two counters. One runs directly from the form (in the main thread) and another is performed by a BackgroundWorker. Interestingly, the two counts do occur at the same time (which is the intent...win!) but the progressbar doesn't change at all until after the BackgroundWorker has finished. Of course, I'd like the progressbar to update during the counting. Help?

Note: The code below is just a test I'm performing so I can take the structure and expand it to a real-world, time-consuming, stored-proc.
Public Class Form1

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Me.ProgressBar1.Maximum = 10
    End Sub

    Private Sub btnHeavyTask_Click(sender As Object, e As EventArgs) Handles btnHeavyTask.Click
        Debug.Print("Starting to count now")
        BackgroundWorker1.WorkerReportsProgress = True
        BackgroundWorker1.RunWorkerAsync()
        mainThreadTask()
    End Sub

    Private Sub mainThreadTask() 
        Dim i As Integer = 0

        Do Until i = 15
            Me.txtHeavyTask.AppendText(vbCrLf + i.ToString)
            Me.txtHeavyTask.Refresh()
            Threading.Thread.Sleep(500)
            i += 1
        Loop

        Me.txtHeavyTask.Text = "Done"
    End Sub

    Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
        Dim i As Integer = 1

        Do Until i = 10
            Debug.Print(i.ToString)
            Threading.Thread.Sleep(1000)
            BackgroundWorker1.ReportProgress(i)
            i += 1
        Loop
        Debug.Print("Counted to " & (i).ToString)
        BackgroundWorker1.ReportProgress(i)
    End Sub

    Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
        Me.ProgressBar1.Value = e.ProgressPercentage.[indent][/indent]ToString
        Me.ProgressBar1.Refresh()
    End Sub


End Class

Open in new window

Avatar of Chris Stanyon
Chris Stanyon
Flag of United Kingdom of Great Britain and Northern Ireland image

It's not the Background worker that finishes before the screen updates - it's the mainThreadTask. Basically, you're running the background task and asking it to update the UI. Straight after you start your background task, you then lock the UI with your MainTheadTask. This means the background task can't update the UI until it's unlocked i.e the main task has finished.
Try it like this
Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
        Me.ProgressBar1.Value = e.ProgressPercentage
        Me.ProgressBar1.Refresh()
End Sub

Open in new window

The Me.ProgressBar1.Value is a integer value and not a string
Avatar of David L. Hansen

ASKER

Fernando,
I changed the code as you suggest and get the same result. I actually commented out all of my code in the "BackgroundWorker1_ProgressChanged" method and then pasted in your code exactly as you wrote it. Do you get something different?
Hi David;

You stated that this is test code. Can you zip that test project up and post it to a web site that we can download it from so I can see what is happening.
At the moment Fernando, I'm just stubbing out this test. I plan to extend it later into the actual real-world app.

Btw, when I remove the "mainThreadTask()" call (just now) the progressbar updated just fine. :)
Hopefully, I can get more out of the BackgroundWorker than just: "I'll let you do your single item of work while updating the progressbar, and I'll just sit here until you are done." I'd like to allow the user to continue with other tasks (like running reports, etc.) while the backgroundworker is busy. I thought I had gotten a BackgroundWorker to do something like that years ago, but perhaps I'm remembering wrong.
SOLUTION
Avatar of Fernando Soto
Fernando Soto
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
David. You sort of have the right idea - the background worker does allow you to call long-running methods in the background by not blocking the UI. This would then allow the user to carry on using the UI. That's the very purpose of the BackgroundWorker.

As I've already said, the problem is not with your background worker. After you've set your BackgroundWorker you then go on to call another long-running task (taskMainThread) but this time you're calling it on the UI, so it makes sense that it would block the UI until it's finished, which is exactly what's happening.

If you really need to run several long-running tasks, then look at using several background workers.

Hope that makes sense (I know threading can take some getting used to)
I've not had a lot of need for asynchronous work over the years; however, when anybody needs it, it's because they have something big and time consuming to run and they don't want their user to just sit there and wait. Answer...Backgroundworker! Yeah! The only problem is...your user can't use the UI until the backgroundworker is done. My confusion is simply that the backgroundworker does indeed free-up the UI while it does it's heavy work...but it frees it up not for the user...but instead for itself (or at least for the progressbar). You can't truly free-up the UI for the user with a single BackgroundWorker.
Do I have that right?
Hi David. I think the main point you're missing here is that the background worker is not your problem. If you have a long running task, then you need to ship that to the background worker which is what you're doing. This then runs that task in the background, meaning that your UI is still responsive. Your users can still click on buttons, scroll through lists, type info into textboxes. That's the job of the background worker, and your background worker is doing exactly that.

The problem comes because, regardless of what the background worker is doing or how long it takes, you (the user) then starts another long-running task on the UI (mainThreadTask in your case). Starting that task is what blocks the UI from any interaction.
Chris,
First, thanks for your patience and your time. I'll try and suppress my ranting...you know the feeling?

Second, I did a test and moved my mainThreadTask call under a new (second button) button_click sub. So now when the backgroundWorker is doing its thing, I click that second button and the progressbar goes silent (frozen) and the mainThreadTask interrupts. So I know that the use of a BackgroundWorker is to keep the UI free (which it does) that is until the user clicks on something. If the UI is free to the user only until he/she actually uses it, what good is the BackgroundWorker?
I suspect the answer to this question is that multiple BackgroundWorkers must be used. Is that the solution (short of using the Async keyword or multiple DoEvents that is)?
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Re: multiple backgroundworkers ... yes, absolutely. As you've already noted, if you have a long running task, then you use a backgroundworker. If you have 2 long runnning tasks then use 2 background workers (assuming they need to tun in parallel).

The async / await pattern is another alternative (as is managing your threads yourself), but they tend to get a whole lot more complicated than a BackgroundWorker.
Got it. Thanks.

I now see that if I kick-off multiple BackgroundWorkers which run processes behind the scenes, all is well (so long as they don't affect the UI). If however, any of the workers attempt to interact with the UI, I get the cross-threading error (which makes sense).

If I run code on the UI simultaneously with a BackgroundWorker, I get the desired effect so long as DoEvents are used in the UI code (as Fernando showed). I'd like to use this approach, however it sounds like doing so is frowned upon. I wonder though, if that could be employed in some situations where the worker and UI actions carry zero risk of stepping on each other (ie. querying the same data, or interfacing with the same objects, etc.).
Hey David. Glad it's starting to make sense.

Just a couple of points to clarify from your last post. Any background process (threaded) that tries to directly interact with the UI will give you cross-threading problems. You need to marshall the interaction back to the UI thread to prevent this. The background worker neatly does that for you in the ReportProgress event handler - it happens behind the scenes but that's effectively what it's doing, which is why you can update the UI (progress bar for example) in that event handerl.

If you're running intensive tasks on the UI thread and calling DoEvents to refresh the screen, then you really should re-think your design strategy. The reason DoEvents is frowned upon is because of what happens behind the scenes and if you don't completely understand it, you're likely to introduce subtle bugs into your code that will be very difficult to track down.

The preferred way of providing a responsive UI to the user is to use async / await as we've already alluded to. When designing your UI and your logic, one guideline to bear in mind - can't remember where I read it - is if a user interaction is likely to take more than 50ms, then run it asynchronously. Your UI will then remain responsive.

In your code example above, the button click would start both tasks asynchronously. You'd then have both tasks running at the same time whilst the UI would remain completely responsive. If you pass an IProgress<T> into your tasks, then you can update the UI in a callback method, in a similar manner to the background worker.
Thanks Chris. Appreciate the help.

Thinking out loud here (not intending to ask more questions for free)....

Sometimes I just want to get away from the progressbar control and provide more specific feedback (a running grand-total for example...or show when a geographical area, like a city or state, has been processed and another has just begun). I've used the progressbar and labels to show percentage feedback on a worker, but never more specific feedback than that (and never such feedback on multiple asynchronous processes). I guess I can just use "e.arguments" to do that, sending that feedback data into textboxes (or listboxes, etc.) right?
Yeah, kind of. The ProgressChanged hander doesn't have an e.arguments property, but the BackgroundWorker.ReportProgress() does have an overload where you can pass in a 'user state'. Currently, you've used it like so:

    BackgroundWorker.ReportProgress(percentage)

But you can use the overload method to pass in any kind of object, such as a string:

    BackgroundWorker.ReportProgress(20, "Some Text")

Now in the ProgressChanged event handler you can access that user state:

    var myState = (e.UserState as string);
    myLabel.Text = myState;

The user state can be any object, so you could create your own class and pass that in:

/// Define a custom class
public class MyUserState
{
    public string Message { get; set; }
    public int StepNum { get; set; }
    public int TotalSteps { get; set; }
}

// In the DoWork method, create an instance of your class
var myUserState = new MyUserState
{
    Message = "Some Message",
    StepNum = 2,
    TotalSteps = 5
}

// and report on the progress, passing in your user state
BackgroundWorker.ReportProgress(20, myUserState)

// In the ProgressChanged event handler
var state = (e.UserState as MyUserState);
label1.Text = state.Message; // Some Message
label2.Text = $("Step {state.StepNum} of {state.TotalSteps}"); // Step 2 of 5
progressBar.Value = e.ProgressPercentage; // 20

Open in new window

That's a very broad overview, but it's the principal of how to pass several pieces of info into your ProgressChange event to allow you to update the UI.

Sorry it's written in C#. Haven't used VB for a long time but the process is very similar :)