BackgroundWorker

BackgroundWorkers are pretty exciting, because they are one of the easiest classes to use, when it comes to running processor intensive operations on a separate dedicated thread. 

Many programmers that wouldn’t even begin to think about getting into multithreading, because of it’s complexities, can actually feel comfortable with the BackgroundWorker, and quickly begin to incorporate multithreading into their applications. 

Because the BackgroundWorker performs its operations on a separate dedicated thread, it prevents your application from freezing up, and becoming non-responsive while it waits for the operations to be completed.  

 

Simulation…

To begin with, let’s create a scenario that simulates intensive processing by calling the System.Threading.Thread.Sleep method, which will block the main thread, causing the application to freeze for 5 seconds. 

Create a new Visual Basic Windows Forms Application project, and name it WorkerDemo.  As soon as the project has been created, add a new TextBox to the form, and a new Button to the form (leave the button named Button1).  Mine looks like this: 
 

form1











Now, double-click the button to create the Button1_Click event handler, and insert a line of code calling the Sleep method, as follows: 
 

Public Class Form1

    Private Sub Button1_Click( _
        ByVal sender As System.Object, _
        ByVal e As System.EventArgs) _
        Handles Button1.Click

        'Insert this line of code, calling
        '  the Sleep method.  5000 = 5 seconds.
        System.Threading.Thread.Sleep(5000)

    End Sub

End Class



Next, run the program (press F5).  Then press the button, and try to enter text into the TextBox.  Can you do it?  Try moving the Form around.  Can you do it? 

If everything worked like it’s supposed to, your application should have froze, and you shouldn’t have been able to do anything for 5 seconds after clicking the button.   

Freezing, obviously, is not the type of behavior you expect from a program.  So let’s look at how we can use the BackgroundWorker to perform our simulated intensive processing operation on a separate dedicated thread.

But before we do, remove the line of code that we added to the Button1_Click event handler. 
 
 

Getting Started…

To start working with the BackgroundWorker class, we need to create a new instance of it at the member (class) level, using the “WithEvents” keyword.  The “WithEvents” keyword causes the instance of the BackgroundWorker to be listed in the “Class Name” drop-down list at the top of the code window, and it’s methods to be listed in the “Method Name” drop-down list.

Using the “WithEvents” keyword is not required to declare and work with the BackgroundWorker, but we will use it for this example.

Before we make our declaration, add an Imports statement for the System.ComponentModel namespace above the Form1 class.

After importing the ComponentModel namespace, add the declaration for the BackgroundWorker at the top of the Form1 class.

Our code should now look like this:

 

'This Import Statement is for the BackgroundWorker
Imports System.ComponentModel


Public Class Form1

    'Create a new instance of the
    '  BackgrondWorker
    Private WithEvents mWorker As _
                New BackgroundWorker()



Now that we have declared our BackgroundWorker, go to the “Class Name” drop-down list at the top of the code window and select “mWorker”.  Then, select “DoWork” from the “Method Name” drop-down list.  This will create the mWorker_DoWork event handler.  Here’s an example:
 

ClassNameDDL

 

 

 

 

 

 

 

 

 

If you performed the above step correctly, you should have a new method named “mWorker_DoWork”.  This method will be executed when the BackgroundWorker’s RunWorkerAsync() method is called.  And, it will execute on a separate dedicated thread.

So, the next thing we need to do is add some code to simulate some intensive processing.  Let’s use the same Sleep method that we used above, because we know that caused our form to freeze for 5 seconds last time.

And here you have it:
 

'This Import Statement is for the BackgroundWorker
Imports System.ComponentModel


Public Class Form1

    'Create a new instance of the
    '  BackgrondWorker
    Private WithEvents mWorker As _
                New BackgroundWorker()


    'This method is executed when
    '  mWorker.RunWorkerAsync is called.
    '  This method will execute on a separate
    '  thread.
    Private Sub mWorker_DoWork( _
        ByVal sender As Object, _
        ByVal e As System.ComponentModel _
            .DoWorkEventArgs) _
        Handles mWorker.DoWork

        'Sleep this thread for 5 seconds.
        System.Threading.Thread.Sleep(5000)

    End Sub



Alright, the last thing we need to do is add the code that is necessary to get the BackgroundWorker running.  Since the BackgroundWorker’s RunWorkerAsync() is the method that starts the processing on a separate thread, we’ll insert that.  If you still have the Button1_Click method in your class, then add it there as follows:
 

'This Import Statement is for the BackgroundWorker
Imports System.ComponentModel


Public Class Form1

    'Create a new instance of the
    '  BackgrondWorker
    Private WithEvents mWorker As _
                New BackgroundWorker()


    'This method is executed when
    '  mWorker.RunWorkerAsync is called.
    '  This method will execute on a separate
    '  thread.
    Private Sub mWorker_DoWork( _
        ByVal sender As Object, _
        ByVal e As System.ComponentModel _
            .DoWorkEventArgs) _
        Handles mWorker.DoWork

        'Sleep this thread for 5 seconds.
        System.Threading.Thread.Sleep(5000)

    End Sub


    Private Sub Button1_Click( _
        ByVal sender As System.Object, _
        ByVal e As System.EventArgs) _
        Handles Button1.Click

        'Calling this method causes the
        '  mWorker_DoWork event handler
        '  to be executed on a separate
        '  thread.
        mWorker.RunWorkerAsync()

    End Sub

End Class


Now that we have the BackgroundWorker all wired up, let’s test it out!  Start your program (F5)…

Can you enter any text into the TextBox?  Can you move the form around?  Absolutely you can!  

Congratulations!  You just learned how to implement multithreading into your application!
 
 

Working with Arguments…

Ok…  It’s really cool to simulate some processing on a separate thread!  However, what we really need is a real-world example.  Because real-world applications do more meaningful things, like performing downloads and databases transactions.

For this real-world example, let’s retrieve a bunch of data from a database, and load it into a DataGridView for display.

You’re welcome to follow along with me, if you want.  I am going to use the SQL Server AdventureWorks database.  SQL Server Express or SQL Server 2005 is required to run it.  Here’s a link to an MSDN walkthrough:

http://msdn.microsoft.com/en-us/library/aa992075(VS.80).aspx


If you’re going to use a different database, then you’ll just have to make sure you have the connection string, the correct provider (OleDb, Odbc, etc.), the query syntax, and enough information to retrieve.

For this example, we will retrieve all of the names in the Contact table of the AdventureWorks database.  There are over 19,000 names in that table, so that should take a little bit of time to retrieve.

Since you already have your form set up, we’ll use that.

This time we’ll start with the Button1_Click method:  Create a string variable to store you SELECT statement in.  Then, pass the string variable as an argument of the RunWorkerAsync() method.  

The RunWorkerAsync() method will actually start the BackgroundWorker on a separate thread, passing the SQL string as an argument.
 

Private Sub Button1_Click( _
    ByVal sender As System.Object, _
    ByVal e As System.EventArgs) _
    Handles Button1.Click

    'Select all of the first and last
    '  names from the Person.Contact
    '  database.
    Dim SQL As String = _
        "SELECT LastName, FirstName " & _
        "FROM Person.Contact " & _
        "ORDER BY LastName, FirstName ASC"

    'Calling this method causes the
    '  mWorker_DoWork event handler
    '  to be executed on a separate
    '  thread.
    mWorker.RunWorkerAsync(SQL)

End Sub



The next thing we need to do is go to the mWorker_DoWork method, and insert code to retrieve the data from the database.  Remember that this method will run on a separate thread.

The following steps needs to be taken:

     -  Retrieve the SQL string that was passed
     -  Create the connection string
     -  Create a table to store the data
     -  Create and configure a DataAdapter
     -  Fill the table using the DataAdapter
     -  Return the filled table to the main thread
 

Here’s our example that accomplishes all of the above:
 

'This method is executed when
'  mWorker.RunWorkerAsync is called.
'  This method will execute on a separate
'  thread.
Private Sub mWorker_DoWork( _
    ByVal sender As Object, _
    ByVal e As System.ComponentModel _
        .DoWorkEventArgs) _
    Handles mWorker.DoWork


    'Retrieve the SQL string that we passed in
    Dim SQL As String = e.Argument


    'Create the connection string
    Dim cnString As String = _
        "Data Source=localhost;" & _
        "Initial Catalog=AdventureWorks;" & _
        "Integrated Security=True;"

    'Create a table to store our data
    Dim table As New DataTable()

    'Create a SqlDataAdapter to retrieve the
    '  data from the AdventureWorks database.
    Using adapter As New  _
        SqlClient.SqlDataAdapter(SQL, cnString)

        'Retrieve the data from the database
        adapter.Fill(table)

    End Using

    'Return the filled table to the main thread
    e.Result = table

End Sub



Important Note!
  The mWorker_DoWork method is being executed on a separate thread.  Therefore, it cannot directly access controls on the form, such as our DataGridView.  Attempting to do so will result in a cross-threading violation.  So instead of filling the DataGridView from within this method, we must pass the filled table back to the main thread using the e.Result property.


The final thing we need to do is add the RunWorkerCompleted event handler to our code window.  This method is fired after the BackgroundWorker has completed it’s work.  Therefore, we can use this method to retrieve the filled table from the BackgroundWorker and set it as the DataSource of our DataGridView.

To do so, go to the “Class Name” drop-down list and select “mWorker”, then select “RunWorkerCompleted” from the “Method Name” drop-down list, as follows:
 

ClassNameDDL2











Now that we have the mWorker_RunWorkerCompleted method added to our code window, let’s go to it and insert code to retrieve the filled table from the BackgroundWorker thread, and use it to fill a DataGridView.

By the way, now’s a good time to go to your form design view, remove the TextBox, then add a DataGridView to the form, leaving it named “DataGridView1".

Here’s an example:
 

'This method is called when mWorker is done
'  This method executes on main thread.
Private Sub mWorker_RunWorkerCompleted( _
    ByVal sender As Object, _
    ByVal e As System.ComponentModel _
        .RunWorkerCompletedEventArgs) _
    Handles mWorker.RunWorkerCompleted

    'Retrieve the filled table from the
    '  BackgroundWorker
    '  thread.
    Dim table As DataTable = e.Result

    'Set the table as the DataSource for
    '  the DataGridView
    Me.DataGridView1.DataSource = table

End Sub



Now that we have all of the code added, let’s take a look at the full Form1 class to see what it looks like:
 

'This Import Statement is for the BackgroundWorker
Imports System.ComponentModel


Public Class Form1

    'Create a new instance of the
    '  BackgrondWorker
    Private WithEvents mWorker As _
                New BackgroundWorker()


    'This method is executed when
    '  mWorker.RunWorkerAsync is called.
    '  This method will execute on a separate
    '  thread.
    Private Sub mWorker_DoWork( _
        ByVal sender As Object, _
        ByVal e As System.ComponentModel _
            .DoWorkEventArgs) _
        Handles mWorker.DoWork


        'Retrieve the SQL string that we passed in
        Dim SQL As String = e.Argument


        'Create the connection string
        Dim cnString As String = _
            "Data Source=localhost;" & _
            "Initial Catalog=AdventureWorks;" & _
            "Integrated Security=True;"

        'Create a table to store our data
        Dim table As New DataTable()

        'Create a SqlDataAdapter to retrieve the
        '  data from the AdventureWorks database.
        Using adapter As New  _
            SqlClient.SqlDataAdapter(SQL, cnString)

            'Retrieve the data from the database
            adapter.Fill(table)

        End Using

        'Return the filled table to the main thread.
        e.Result = table

    End Sub


    'This method is called when mWorker is done
    '  This method executes on main thread.
    Private Sub mWorker_RunWorkerCompleted( _
        ByVal sender As Object, _
        ByVal e As System.ComponentModel _
            .RunWorkerCompletedEventArgs) _
        Handles mWorker.RunWorkerCompleted

        'Retrieve the filled table from the
        '  BackgroundWorker
        '  thread.
        Dim table As DataTable = e.Result

        'Set the table as the DataSource for
        '  the DataGridView
        Me.DataGridView1.DataSource = table

    End Sub


    Private Sub Button1_Click( _
        ByVal sender As System.Object, _
        ByVal e As System.EventArgs) _
        Handles Button1.Click

        'Select all of the first and last
        '  names from the Person.Contact
        '  database.
        Dim SQL As String = _
            "SELECT LastName, FirstName " & _
            "FROM Person.Contact " & _
            "ORDER BY LastName, FirstName ASC"

        'Calling this method causes the
        '  mWorker_DoWork event handler
        '  to be executed on a separate
        '  thread.
        mWorker.RunWorkerAsync(SQL)

    End Sub

End Class


Ok, so are you ready to run it?  Let’s give it a whirl.  Start your application (F5).  When the form comes up, press the button to fill the DataGridView.

You’ll notice that the form is completely responsive while the data is being retrieved on a separate thread using the BackGroundWorker.  The user is free to move the form around, and interact with any other controls that may be on the form.

Here’s what my form looked like after the data was retrieved:
 

form2




























Summary

The BackgroundWorker class in the System.ComponentModel namespace is very easy to use to perform processor intensive operations on a separate dedicated thread.

With very little knowledge of threading, you can begin to implement multi-threading into your applications, greatly enhancing their responsiveness during time-consuming operations.

For more information on the BackgroundWorker, please see the MSDN Documentation at http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

0 comments:

Leave a Reply

Translate

Google-Translate-Chinese (Simplified) BETA Google-Translate-English to French Google-Translate-English to German Google-Translate-English to Italian Google-Translate-English to Japanese BETA Google-Translate-English to Korean BETA Google-Translate-English to Russian BETA Google-Translate-English to Spanish

Tags