Article Options
Recently Viewed
Premium Sponsor
Premium Sponsor

 »  Home  »  .NET Framework  »  Framework 2.0  »  Multithreading The Easy Way: The BackgroundWorker  »  Giving User Feedback - ProgressChanged
 »  Home  »  .NET Framework  »  Framework 3.0  »  Multithreading The Easy Way: The BackgroundWorker  »  Giving User Feedback - ProgressChanged
 »  Home  »  .NET Intermediate  »  Multithreading The Easy Way: The BackgroundWorker  »  Giving User Feedback - ProgressChanged
 »  Home  »  Visual Studio 2005  »  Multithreading The Easy Way: The BackgroundWorker  »  Giving User Feedback - ProgressChanged
 »  Home  »  Visual Studio 2008  »  Multithreading The Easy Way: The BackgroundWorker  »  Giving User Feedback - ProgressChanged
 »  Home  »  Windows Development  »  Visual Basic 2005  »  Multithreading The Easy Way: The BackgroundWorker  »  Giving User Feedback - ProgressChanged
 »  Home  »  Windows Development  »  Windows Presentation Foundation  »  Multithreading The Easy Way: The BackgroundWorker  »  Giving User Feedback - ProgressChanged
Multithreading The Easy Way: The BackgroundWorker
by Ged Mead | Published  06/01/2008 | Framework 2.0 Framework 3.0 .NET Intermediate Visual Studio 2005 Visual Studio 2008 Visual Basic 2005 Windows Presentation Foundation | Rating:
Giving User Feedback - ProgressChanged

  Because we took the step of setting the WorkerReportsProgress property to True, we can harness this to, well, report progress actually! 

  There are several ways we can keep the user informed.  Often a ProgressBar will be used and updated as the task reaches various stages.  Although we could take this route for the current project, calculating an exact count or time estimate for the task can sometimes be tricky, so I decided just to go feedback that displayed the files in a listbox as they are found.   Later I'll add some simple code that gives a clearer indication that the process is still working. 

  For additional user clarity I thought that what I would do is to create a new form as soon as the BackgroundWorker starts its task and get it to report progress directly back to this form.  (As opposed to displaying on the form I used to create and control the BGW ).

  This way, the user can minimise the form that shows progress if they want to so that the progress state isn't constantly in their face.

  So the next step will be to create a second form, called  frmProgress and to place a large ListBox on it.  (You could fill the whole form with the ListBox if you prefer, but I plan to add a label later to give the user more feedback, so have left some space at the bottom of the form).  

  Outside any of the form's procedures declare a variable for a frmProgress:

Code Copy
Dim fp As frmProgress

 

  The reason for doing it this way is that you will want to refer to this frmProgress  instance from elsewhere in Form1, so you need the scope to be form wide.

  Now you can instantiate a new frmProgress from the click event of the start button:

Code Copy
Private Sub btnStart_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStart.Click
        '  Show progress form
        fp = New frmProgress
        fp.Show()

        bWkrFileCheck.RunWorkerAsync()

End Sub

 

   I found that I really wanted the progress form to be placed neatly to the right hand side of Form1, so I also added this optional code to the button's click event:

Code Copy
fp.Left = Me.Left + Me.Width + 1
fp.Top = Me.Top

 

 which takes care of that.  Well, it should, but if you find that the progress form stubbornly insists of plonking itself on top of the calling form then you'll  need to either change the StartPosition property in the Properties Window or add this further line of code:

Code Copy
fp.StartPosition = FormStartPosition.Manual

  Next we need the code that will actually send the feedback to this form.

  If you refer back to the screenshot that showed the events available for the BGW you will see there is one named ProgressChanged.  This is the procedure in which we will place the code that actually updates the ListBox. 

Code Copy
Private Sub bWkrFileCheck_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles bWkrFileCheck.ProgressChanged
        fp.ListBox1.Items.Add(e.UserState)
End Sub

 

   However if you make this edit and run the project however you won't be rewarded with a display of files in the ListBox;  a result which may or may not surprise you.  

    The reason is that the above event will only fire when it is informed that there has in fact been a change of some kind that is classifed as a change in progress.  So what is needed is a trigger elsewhere which will cause this event to fire.  That trigger is the ProgressChanged method of the BackgroundWorker which in this example we will place inside the FileFinder procedure.

  Here is the amended code for this requirement:

Code Copy
For Each fname As String In Directory.GetFiles(dir)
   If fname.EndsWith("txt") Then
     '  pass back progress message
        bWkrFileCheck.ReportProgress(0, fname)
   End If
Next

 

  You will see that the ReportProgress method has two parameters - an Integer and also what appears to be a String.  The second parameter is in fact an Object, but as we have already declared fname as a string, this is the type that will be passed to the event.

  The first parameter is PercentProgress and you can assign a value to this to represent how far the process has moved through the task.  As I mentioned earlier, I've chosen not to implement this feature so I pass back a value of zero each time.   If you decide that you do need this for a project of your own, then you simply calculate the percentage at any given moment, assign this value to PercentProgress and use it in the ProgressChanged event as you wish (e.g. as a means of updating a progress bar).   

  The second parameter is UserState.  In this example I have chosen to assign the string that is the name of each file found to UserState.   Because ReportProgress runs each time the FileFinder succeeds in finding a file that meets the criteria, this of course means that all matching file names will be passed back in turn and can be listed in the ListBox.

   I hope that all makes sense.  You may be wondering at this point why it is necessary to take this long way round the task.  Why not simply add code such as:

Code Copy
  For Each fname As String In Directory.GetFiles(dir)
    If fname.EndsWith("txt") Then
     '  pass back progress message directly?
        fp.ListBox1.Items.Add(fname)
    End If
  Next

 

to the FileFinder procedure in order to update the ListBox?

  If you are curious I suggest you try it.   As you will see, the problem is that because you are now running the application using two separate threads the background thread can't "see" the main thread and so has no direct knowledge of the progress form and its ListBox.   The BackgroundWorker has to play that middleman role in order for the two threads to be able to communicate data between each other.

   As a matter of usability, one thing I don't particularly like about the way the ListBox is populated is that once its visible height is filled with file names, there is little indication that something is still happening.  In fact, the vertical scrollbar changes size as the list lengthens but this isn't particularly obvious.   So I will make some minor improvements to deal with this.

  First, try adding a new line to the ProgressChanged event as shown below:

Code Copy
    Private Sub bWkrFileCheck_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles bWkrFileCheck.ProgressChanged
        fp.ListBox1.Items.Add(e.UserState)
        fp.ListBox1.SetSelected(fp.ListBox1.Items.Count - 1, True)
    End Sub

Then run the project again.

   This time it's very obvious that the process is working away.   In fact, you might even think it's too obvious and might dislike the way the highlight flashes on and off as it updates.  If so, you can add yet another line:

 

Code Copy
    Private Sub bWkrFileCheck_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles bWkrFileCheck.ProgressChanged
        fp.ListBox1.Items.Add(e.UserState)
        fp.ListBox1.SetSelected(fp.ListBox1.Items.Count - 1, True)
        fp.ListBox1.SetSelected(fp.ListBox1.Items.Count - 1, False)
    End Sub

 

and this will fix that issue.  If you download the sample solution that I have included with this article you'll see that I have also added a label that gives the user some additional feedback.

  When you run this project now, you can check that the main thread (and your Form1) is fully accessible to the user by, for example, simply moving the form around the screen in the usual way - something you wouldn't be able to do if the project was choked up on the long running task.  

  Again, in the sample solution I have included a better means of checking that everything is running smoothly.  At the click of the Start Button, a third form is instantiated and shown and this form contains a calendar and a label.  The user can access the calendar and will see feedback from their actions.  This will happen while the ListBox on the progress form still continues to be updated.

  

 

 

 

Comments    Submit Comment

Comment #1  (Posted by an unknown user on 06/04/2008)
Rating
Very clear and easy to follow. AND the sample Worked!!!!
 
Comment #2  (Posted by RAB on 06/12/2008)
Rating
Can we use teh Backgroundworker component without a form? Can it be instantiated in a module?
 
Comment #3  (Posted by Ged Mead on 06/20/2008)
Rating
Yes you can do that. However as there is no toolbox available for a Module you have to create the bgw in code. To do this you use Dim bgw as New System.ComponentModel.BackgroundWorker.
You will of course also need to set the properties and add handlers in code.
If you need more info on this question please post a question in the vbcity VB.NET forums (where it's easier for me to post code samples) :-}
 
Comment #4  (Posted by an unknown user on 07/30/2008)
Rating
Thank a lot XTab
 
Comment #5  (Posted by an unknown user on 08/08/2008)
Rating
Great step-by-step with in-line code. Ged obviously put alot of time in this well written article.
More please !
 
Comment #6  (Posted by an unknown user on 08/13/2008)
Rating
I try to read everything you publish. Great stuff.
 
Comment #7  (Posted by an unknown user on 08/18/2008)
Rating
Excellent and simple to use.
 
Comment #8  (Posted by an unknown user on 09/06/2008)
Rating
practical, readable
 
Comment #9  (Posted by an unknown user on 09/29/2008)
Rating
This guide was one of the best guides I've read!

You just helped me understand something I origionally had no clue about. Thanks!
 
Comment #10  (Posted by Matt Higginbotham on 10/31/2008)
Rating
Thanks Ged! What a great article.. This helped me a bunch with some long database queries that i have.

Keep Em' Coming
 
Comment #11  (Posted by an unknown user on 11/03/2008)
Rating
Very good except the cancelation did not work correctly, although it cancelled the task if I cancelled it and then re-ran I got "Operation has already had OperationCanceled called on it". I need to reset the canelled property at finish but, its read only any ideas?
 
Comment #12  (Posted by an unknown user on 12/01/2008)
Rating
eltroeralboc
 
Comment #13  (Posted by an unknown user on 12/12/2008)
Rating
Eays to understand. Right to the point, with some sence of humor.
Thank you.
 
Comment #14  (Posted by an unknown user on 12/21/2008)
Rating
very good... this is what i'm searching for.
 
Comment #15  (Posted by Jimbo on 12/28/2008)
Rating
What if I want the backgroundworker to KEEP working, and watch the folder for changes? Do I just put a loop in the Sub bWkrFileCheck_DoWork, this might just keep populating the window over and over though :-) Thanks in advance, this is great stuff!
 
Comment #16  (Posted by an unknown user on 12/29/2008)
Rating
Great article! Answered many of my basic questions about how to use that control.
 
Comment #17  (Posted by busrider on 01/04/2009)
Rating
eFront-???????? ??????? ??????? ? ???????????? ??????? www.e-front.com.ua
 
Comment #18  (Posted by an unknown user on 02/14/2009)
Rating
Great conceptual explanations and screenshots. I didn't see a single file that had all the code together. Even through my jumping around (adapting it to VB Express), I could still follow both the details and the general direction of the project. Great job!
 
Comment #19  (Posted by an unknown user on 03/27/2009)
Rating
I am relatively new to coding, and backgroundworker is a leap for me. This great article makes it possible to understand how each part of bkwkr works together. The one thing, for my project, I can't seem to do is to make it so that only the file names list in the listbox instead of the whole path. I've done this plenty of times before but for some reason no place I'm trying to do this is giving me anything other than the directory folder (above the files) listed multiple times. Not the file names themselves. Any ideas?
 
Comment #20  (Posted by Ged Mead on 03/28/2009)
Rating
If you use my sample code, at the point where the variable 'fname' is used to pass the full path to the listbox, change this to a new string. You can strip out the path from fname by using LastIndexOf, with the backslash as the argument. Then you can pull out the chars from the last backslash to the end of the path (i.e the file name only). Finally you hand this shortened string back to fname. Hope this helps.
 
Comment #21  (Posted by Gerald Grenade on 04/17/2009)
Rating
I have to agree with everyone here and thank you for this excellent presentation - I have been browsing the net and read 5 books in Visual basic - Never found a topic so cleary explained - Just wanted to know if you have covered something regarding threading - I am planning to do some serial com and i want to update a form - but i get cross thread error because i am trying to access a form control from another thread that instantiated it - the background worker method is a solution - if you have some times .. Thanks again for the excellent write up - i look foreward to see other posts from you
 
Comment #22  (Posted by an unknown user on 05/11/2009)
Rating
I am newbie to VB. Ur articles hav helped me a lot, they are just they way i wanted ....Thanks :):)
 
Comment #23  (Posted by an unknown user on 06/11/2009)
Rating
Gave me the exact information I needed to perform lengthy processes.
 
Comment #24  (Posted by Onur on 08/06/2009)
Rating
Dear Ged,

Very good article of you, however i want to express something about experiments related to Backgroundworker. Though Backgroundworker provides us an easy way to run more than one task at the same time, if you have a CPU-intensive job, the UI may get blocked or the UI (form) may not be moved or minimized because of not being able to process messages due to %100 CPU consumtion.

For example, on my crappy p4 2.4 single-core machine, you cannot move window while task is in progress:

Private Sub bgworker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgworker1.DoWork
For x As Integer = 0 To 50000
bgworker1.ReportProgress(x)
Threading.Thread.Sleep(1)
Next
End Sub

Private Sub bgworker1_pchanged(ByVal sender As System.Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles bgworker1.ProgressChanged
Label1.Text = e.ProgressPercentage

End Sub

Private Sub bgworker1_finished(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bgworker1.RunWorkerCompleted
Label2.Text = "Done!"

End Sub

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Label1.Text = "Waiting.."

bgworker1.RunWorkerAsync()
End Sub

The only chance to move form, is to use Sleep to let Windows process move message:

' In DoWork Event:

Private Sub bgworker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgworker1.DoWork
For x As Integer = 0 To 50000
bgworker1.ReportProgress(x)
' One milisecond is enough on my machine
Threading.Thread.Sleep(1)
Next
End Sub

However, on some powerful multi-core CPU-having PCs, it may not be required to pause execution to let CPU breathe for a milisecond.

Just my point of view,

Hope you reply,

Onur
 
Comment #25  (Posted by Onur on 08/06/2009)
Rating
Related to my provius comment, i meant "i cannot move form" while task is in progress (counting) if i don't use Thread.Sleep in DoWork event of BGW due to %100 cpu usage on my old P4 2.4 GHZ machine. So, you can test it by omitting "Thread.Sleep" in the code i've previously posted.

Thanks.

Onur
 
Comment #26  (Posted by an unknown user on 08/08/2009)
Rating
Great for Windows apps. Is there also a good example of how to get this to work with web apps in VB.NET?
 
Comment #27  (Posted by on 10/11/2009)
Rating

 
Comment #28  (Posted by an unknown user on 10/26/2009)
Rating
Explained the BackgroundWorker multithreading Class in a straightforward and easy to follow manner. Excellent for the newcomer to multithreading.
 
Comment #29  (Posted by an unknown user on 10/29/2009)
Rating
It contained everything I was looking for in an easy to understand way..
 
Comment #30  (Posted by an unknown user on 10/31/2009)
Rating
Easy to follow!
 
Comment #31  (Posted by myintnaing on 11/04/2009)
Rating
I am IT Student

 
Comment #32  (Posted by Of these, M1 is the most significant since on 11/24/2009)
Rating
I really do like this place.: URLsWithNothing
 
Sponsored Links