Tuesday, July 31, 2012

[ C# ] - Using BackGroundWorker Component with ProgressChange and Other UI Updates


1. Introduction


BackgroundWorker component is used to perform long running tasks in the background while the application running in the foreground will still look for the user events and responds to them. Usually when application is busy (i.e.) performing a long running task, users of the application cannot interact with the UI elements and the only existing thread is engaged with that long running task.

Here, we created a sample that counts vowels of the textual content given in the text box. To make that as a long running task, we asked the execution to sleep for 50 milliseconds after processing a single character.



2. About the Sample


The sample created using the Background worker thread is shown below:

Pic1: BackgroundWorker Example App


When you click the Start Count the sample will start counting the vowels and displays that in the bottom count meters (i.e.) in the text boxes for A, E, I, O, U. In the meantime a progress bar will show the progress of the operation. The counting process is made intentionally slow, by making use thread.sleep. The cancel button is provided to stop the counting process in the middle.

When both the check boxes are in un-checked state and, when the sample is counting the vowels, you cannot interact with the form. The “Use Doevents“ checkbox will apply doevents and shows you that you can interact with the form. But, when there is an interaction, counting process stops and after interaction completed the counting resumes. Use heavy interaction like moving the form or resizing the form.
The “Use Background Worker” option will overcome the drawback of do only one stuff at once (i.e.) now with the back ground worker component you can see the progress update and vowels count meter increasing continuously even when you resize the form.

Source Code Search Tag: //BWSamp



3. Vowel counter functions


The below function checks and increments the vowel count for A.

//BWSamp 01: Functions that count and Updates vowel counters.
private void CheckVowelA(char c)
{
    long Vowel_count;
    if (c == 'A' || c == 'a')
    {
        if (chkUseBackground.CheckState != CheckState.Checked)
        {
            long.TryParse(txtA.Text, out  Vowel_count);
            Vowel_count++;
            txtA.Text = Vowel_count.ToString();
        }
        else
            count_a = count_a + 1;
    }
}

The CheckVowelA function checks the passed in value to make sure it is a vowel A. Also note that when the “Form” is in background thread mode we just increment a variable count_a. When we are not using the Back Ground thread component, the vowel meter for A is incremented (i.e.) the text box for A is set with the incremented value. A similar function is written for all other four vowels.

At present you may have a question, why we have a separate variable usage count_a for background worker component. I will come to that point later in this article.



4. Add the BackgroundWorker Component


When we know that the form should do a long running task in the background and in the mean time user can still interact with the form to do some useful work, the BackgroundWorker component is a best choice to deal with such a situation. The video given below shows adding the Background Worker component:



Video Steps
1) Adds the BackgroundWorker component to the Form
2) Sets the name for the component
3) Then provided handler for all three events supported by it.

BackgroundWorker is a component and that is why it does not occupy any space in the form. The components unlike “Controls” don’t have visual appearance and hence the components go and sit at the bottom of the form designer when it is dropped in the form. In the video we handled three events and we will explore that later.

Note that after adding the component we should set two important properties that will enable the stopping the worker thread when the task is in progress, sending the progress of the running task. These two properties are shown below:

Pic2: WorkReportsProgress




5. Class Scope variables


We are using cancel_work variable to stop the vowel counting operation. This variable is used in counting operation when backgroundworker component is not used. The variables count_a, count_e, count_I, count_o and count_u are used to keep track the vowel counts in the background work mode vowel counting action. Below are declarations:

//BWSamp 04: Cancel Action
private bool cancel_work = false;
long count_a, count_e, count_i, count_o, count_u;



6. Start Count Handler


In the start count button handler we are initializing the vowel counting variables and then setting the cancel_work to false. Based on the chkUseBackground checkbox state we start the vowel counting operation either in single threaded mode or two threaded mode. The call to DoWithBKGrd function will not use any background thread while performing the count operation and the function operates in single thread mode. The call RunWorkerAsync() that we make on the backgroundworker component (Named as BckThread) will call the DoWork handler of the same component. At present we don’t have that handler and provide that in a short moment. This dowork runs on a separate thread and all the rest of code runs in a different thread. Below is the code for Start button click

//BWSamp 02: Simulate Long Running Task
private void btnStart_Click(object sender, EventArgs e)
{
    //2.1: Set Cancel Work as false when the job starts
    cancel_work = false;
    count_a = 0;
    count_e = 0;
    count_i = 0;
    count_o = 0;
    count_u = 0;

    //2.2: To use back ground worker, call RunWorkerAsync
    if (chkUseBackground.CheckState != CheckState.Checked)
    {
        DoWithoutBKGrd();
    }
    else
    {
        BckThread.RunWorkerAsync();
    }
}



7. Long Running Task – Without Background Worker


In this section I am going to explain the source code implementation for the DoWithoutBKGrd Function.

1) From the textbox we are getting the text content into a string variable called strcontent and length of the strcontent is stored in the len variable. Then we divide the total lengths into 100 parts, storing the length single part in a variable Prog_Inc. This variable is useful to show the status of the operation in a progress bar in an accurate manner (i.e.) if progress is in the middle 50% of job completed.

//3.1: Get text length
string strcontent = txtContent.Text;
long len = strcontent.Length;
int Prog_Inc = (int)(len / 100);

2) As we are going to count vowels in the set of paragraphs displayed in the big text box, it will be useful to convert all the string content to a char array. The below piece of code is doing just that.

//3.2: Copy the string content to char array
char[] chars = strcontent.ToCharArray();

3) Once we have text box content in the char array format, we can examine each of those letters to see whether it is a vowel or not. We are using a loop to iterate through the entire letter extracted from the textbox into an array. For each letter we are making call to CheckVowelA-U. And those functions will do the required stuff (Refer Section 3). Note that in each of the iteration we check the cancel_work variable, and when it is set we reset the counter and return back. Below is the piece of code:

//3.3: Iterate through each char and increment the Vowels count
for (int itr = 0; itr < len; itr++)
{
    if (cancel_work == false)
    {
        char c = chars[itr];
        lblEvtComplete.Text = "Count In-Progress";

        //3.3.1: Count for A
        CheckVowelA(c);

        //3.3.2: Count for E
        CheckVowelE(c);

        //3.3.3: Count for I
        CheckVowelI(c);

        //3.3.4: Count for O
        CheckVowelO(c);

        //3.3.5: Count for U
        CheckVowelU(c);
.
.
.
.
else
{
    txtA.Text = "0";
    txtE.Text = "0";
    txtI.Text = "0";
    txtO.Text = "0";
    txtU.Text = "0";
    lblEvtComplete.Text = "Counts Cancelled";
    return;
}

4) The Prog_Inc variable holds a value that says when should we do a progress increment of one unit in terms of number of letters processed for vowel counting. If we divide the current progress (the loop counter) by the Prog_Inc, we will get the progress percentage. The variable prog_value holds the progress bar percentage and sets that to the progress bar. The 10 Milli seconds sleeps is to simulate this counting operation as long running operation. In this long running iteration loop we called the DoEvents, when the Use DoEvents checkbox is checked.

//3.4: Update Progress bar
int prog_value = (itr / Prog_Inc);
if (prog_value > 100)
    prog_value = 100;
ProgressB.Value = prog_value;
Thread.Sleep(10);
if (chkUseDoevents.CheckState == CheckState.Checked )
    Application.DoEvents();

The doevent() function call does not spawn a different thread and it runs in the same thread. But do event will stops current counting operation and performs all the pending UI tasks (Say the user resized the form while vowel counting in progress), then resumes back to the counting operation.

5) When we check the check box stating we want to use the back ground thread for counting the vowels, the use do events option gets disabled. The code for this is listed below:

//BWSamp 06: When using BkGrd component, uncheck and disable doevents
private void chkUseBackground_Click(object sender, EventArgs e)
{
    if (chkUseBackground.CheckState == CheckState.Checked)
    {
        chkUseDoevents.Enabled = false;
        chkUseDoevents.CheckState = CheckState.Unchecked;
    }
    else
        chkUseDoevents.Enabled = true;
}



8. Canceling the Long running Operation


As Already told, the simulated long running operation can be cancelled in between by clicking the cancel button. We set the cancel_work variable to true and this variable is used by the counting operation running on the same thread. When the counting operation is performed on the background thread, we are making a call to the BackgroundWorker component’s member function CancelAsync. This function call will set the Background Worker component’s CancellationPending property to true and this property is checked in the long running task.

//BWSamp 05: Set the Cancel Flag to cancel the current active job
private void btnCancel_Click(object sender, EventArgs e)
{
    cancel_work = true;
    if (chkUseBackground.CheckState == CheckState.Checked)
        BckThread.CancelAsync();
}



9. DoWork event handler of BackgroundWorker


There are three events important event that you should handle (Two are optional actually. Loon at the Pic2) and these events are:
1) DoWork
2) ProgressChanged
3) BckThread_RunWorkerCompleted

The dowork event will actually fired by the control when we call background worker thread component’s method RunWorkerAsync. You can perform the long running task in the event handler for the DoWork event. This event handler runs on the separate thread and while other two runs on the thread that starts the background worker thread. In out case the form is the main thread that spawns the UI thread by making a call to RunWorkerAsync in the Start Count button click.

Pic 3: Interaction Between Main Thread and Background Thread



In the above picture, the main thread (The form) starts the background thread (sub thread), which is nothing but the doWork event handler of the backgroundworker component. Inside this modifying the UI components of the main thread is not allowed. Say for example we want to show the progress of the dowork in the progress bar and like to update vowel counts real time when counting is performed. But we cannot access those two user interface controls of the form from the Do Work handler as it is running in the separate thread.

But the do work handler is helps the Main thread through the ProgressChanged and RunWorkerCompleted events. Note that these two events are raised by the worker thread and handled by the main thread. These handlers will run on the main thread so we can do whatever UI updates we like to do. The worker components function call ReportProgress will raise the event ProgressChanged. Once the dowork handler returns, the event RunWorkerCompleted will be raised.

Let dig into the code given in the do work handler of the background component.

1) After getting the length of the entire string content, we are dividing that string length into 100, so that we can inform the progress from 0 to 100. Below is the piece of code:

//7.1: Get text length
string strcontent = txtContent.Text;
long len = strcontent.Length;
int Prog_Inc = (int)(len / 100);

2) Next we convert entire string into a character array, then process the letters one by one and make sure it is vowels. When it is vowel we increment the count meter by one inside vowel checking functions. Below is the code (Almost equal to what we did in section 7) for it:

//7.2: Copy the string content to char array
char[] chars = strcontent.ToCharArray();

//7.3: Iterate through each char and increment the Vowels count
for (int itr = 0; itr < len; itr++)
{
    char c = chars[itr];
                                      .
                                      .
                                      .
             //7.3.2: Count for A
CheckVowelA(c);

//7.3.3: Count for E
CheckVowelE(c);

//7.3.4: Count for I
CheckVowelI(c);

//7.3.5: Count for O
CheckVowelO(c);

//7.3.6: Count for U
CheckVowelU(c);

3) In the iterations for each letters, we check for the CancellationPending property to make sure main thread wants to terminate the current work that is in progress. Note that the owner of this do work thread can terminate this thread by anytime by making a call to the method CancelAsync of the backgroundworker component. The above said function call will set the CancellationPending property of the Backgroundworker to true. When we see there is a cancellation pending we simply return from the background thread. Below is the piece of code

//7.3.2: Check the Cancel State
if (BckThread.CancellationPending == true)
{
    e.Cancel = true;
    return;
}

4) After processing each letters, we report the progress by making the call to the method ReportProgress. This method will fire the event ProgressChanged and you know that we already have the handler for that in the form (Not yet implemented). You can refer video1 towards its end. Prog_inc is 1/100th part of length of the string and we calculate how much we progressed in the prog_value using the Prog_Inc. We send this calculated current progress value to the ReportProgress function. Even though we cannot modify the UI elements from the Background Job thread, we can do the same from the event handler for ProgressChanged as this handler runs in the context of main thread (Refer Pic3).

//7.4: Update Progress bar
int prog_value = (itr / Prog_Inc);
if (prog_value > 100)
    prog_value = 100;
BckThread.ReportProgress(prog_value);
Thread.Sleep(10);



10. Updating the UI in the Progress Changed Event Handler


The code in the Progress Changed event handler is straightforward. We get progress percentage through the ProgressChangedEventArgs that we receive as argument to the event handler. In the progress changed function we set the progress bar value and in the mean time we also update the vowel count meters based on the values in the class level variables. Remember? We pass Prog_value to the function BckThread.ReportProgress(prog_value), and that value is collected here as event handler argument.

//BWSamp 08: Handle Progress Changed of Bck Worker Thread
private void BckThread_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    ProgressB.Value = e.ProgressPercentage;
    txtA.Text = count_a.ToString();
    txtE.Text = count_e.ToString();
    txtI.Text = count_i.ToString();
    txtO.Text = count_o.ToString();
    txtU.Text = count_u.ToString();
}



11. Updating the UI through Runworker completed Handler


The code below shows setting a label with the notification of back ground task completed in the handler for the RunWorkerCompleted. There is nothing special that needs to be discussed here.

//BWSamp09: Handle Bck Task completed
private void BckThread_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (e.Cancelled == true)
        lblEvtComplete.Text = "Cancelled";
    else
        lblEvtComplete.Text = "Completed";
}



12.  Running the Sample Application


1) First the sample is run in a normal mode. That is we run the sample without background worker component and we don’t used do DoEvent also. In the video you can see the counting operation is started and after that a form resize is tried. This makes the form to move to Not Responding stage, and once the counting operation is completed you get the updated vowel count meter.



Video Steps
1) The sample exe is started from the explorer
2) After showing the form, start count button is clicked
3) Clicking on the Window Restore option makes the sample in Not Responding Stage (i.e. it is busy with counting vowels)
4) Clicking the cancel button Fades-out the form (Kind of dead) informing the user that sample is not ready and still busy.
5) Waiting for the counting to be completed. Once done the form will become alive


2) The above video shows the need for Back ground work. In the below video the usage of DoEvent is shown. You can notice, even though the Progress Bar and Count meter update is smooth, when we move the form for three or four seconds, the count operation is suspended and resumes back when the move or resize operation of form is completed. This is shown as visual indication as when the for is moved the progress bar and count meter update is stopped. Check this in the below video.



Video Steps
1) Sample application is started
2) Use DoEvents check box is checked
3) Shows the Progress Bar Increment and Count Meter update
4) Resizes the form and shows the continuous break in the Progress and Count meter Update.

Somewhat better compared the previous as this time we use the DoEvent on long running counting operation.


3) In this last video we used the BackgroundWorker component to show how smooth and real-time the operation of counting as well as updating the Foreground UI form. When you resize or move the form you can see the un-interrupted counting as well as progress update to the Form UI. This is the power of BackGroundWorker component. What we shown here is a simple sample that perform vowel count intentionally slow. In real world, the background will do some serious task not blocking the foreground UI, and there by user can still interact with the form and do some useful application interaction while the work started by him/her is still going on the back ground.



Video Steps
1) Sample is started
2) Use Background Worker check box is selected (DoEvents disabled)
3) Start Count Button is clicked
4) Form is Resized, text box content is changed while back ground job is going on


Source Code: Download



2 comments:

  1. Excellent Post... Thanks much for sharing :)

    ReplyDelete
  2. I used to be recommended this website by means of my cousin.
    I am no longer sure whether this submit is written by way of him
    as nobody else recognize such particular approximately my trouble.

    You're amazing! Thank you!
    My weblog : Personal Trainer Mission Valley

    ReplyDelete

Leave your comment(s) here.

*** When a New Article posted, get Notification to the email that you give below: ***

DISCLAIMER

The opinions expressed herein are my own personal opinions and do not represent my current or previous employer's view in anyway