KnowDotNet Visual Organizer

BackgroundWorker Class in the 2.0 Framework

by William Ryan
Print this Article Discuss in Forums

Recently, I've had a little bit of time and started playing with Visual Studio .NET 2005 again.  One of the features that I really like is the BackgroundWorker Class.  Now, you could always create your own background thread and process whatever you felt like with it, but it got a lot easier.  

People like Jon Skeet have long advocated that all data access processing be facilitated in another thread other than the main UI thread.  There are many benefits to this type of approach but it invites the inherent difficulty of using threading. In this article, I'll work through a brief walkthrough of using the BackgroundWorker to fill a DataGrid.  

One of the golden rules of using Winforms and Threading is that you don't touch anything created on one thread in another one.  I'll touch upon this in a second....

First, all you really need to use the BackgroundWorker is dragging a component onto your form, and at a minimum adding code to handle the DoWork event.  Look at the following code snippet:

private void worker_DoWork(object sender, DoWorkEventArgs e)
{
    
using (SqlConnection cn = new SqlConnection(ConnectString))
     {
        
SqlCommand cmd = new SqlCommand("SELECT * FROM CUSTOMERS", cn);
         dt =
new DataTable("Customers");
        
SqlDataAdapter da = new SqlDataAdapter(cmd);
         da.Fill(dt);
     }
      
lock(dataGridView1){
        dataGridView1.DataSource    = dt;
       }
}

private void btnLoadData_Click(object sender, EventArgs e)
{
     worker.RunWorkerAsync();
}

Note that dataGridView1 is a DataGridView that I dragged onto the form.  Can you see what's wrong with this code?  A common mistake many newcomers to threading make is that they think all it takes to ensure thread safety is wrapping an object in a lock statement.  But if you run this code, you'll get the following error:

ThreadingError


May I gently point out that the dataGridView1 was in a lock statement - yet the exception is still thrown.  That should pretty much dispell the whole myth of lock being a panacea to all things threading.

So what's the problem?  The ErrorMessage spells it out clear as day.  We're accessing a Winforms control from a thread other than the one that it was created one.  Remember that the next time someone tells you that Winforms are Thread safe.

So how do we make this code work?  Well, we're going to trap another event, RunWorkerCompleted.  At this point, the callback has returned and it's safe to access the dataGridView object or any of the other controls on the forms.  So a slight modification is all that's needed to get it to work:

private void worker_DoWork(object sender, DoWorkEventArgs e)
{
      
using (SqlConnection cn = new SqlConnection(ConnectString))
      {
          
SqlCommand cmd = new SqlCommand("SELECT * FROM CUSTOMERS", cn);
          dt =
new DataTable("Customers");
          
SqlDataAdapter da = new SqlDataAdapter(cmd);
          da.Fill(dt);
      }
            
//Removed the code setting the datasource here.
}
private void btnLoadData_Click(object sender, EventArgs e)
{
      worker.RunWorkerAsync();
}

private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{  
      
//Will be called when the DoWork is completed
      LoadData();
}

private void LoadData()
{
      
//No exceptions here.
      dataGridView1.DataSource = dt;
}

Now, if you think Winforms aren't threadsafe (they aren't), go ahead and really have some fun and try tapping your connection object (assuming you aren't working under the 2.0 Framework which supports MARS) from another thread.

Yes, you can accomplish all of this in the previous versions of the framework using a few different methods but it sure is a lot easier now.

Writing Add-Ins for Visual Studio .NET
Writing Add-ins for Visual Studio .NET
by Les Smith
Apress Publishing