SUMMARY
This step-by-step article shows you how to query a database
on a background thread and use databinding to display the results in a
DataGrid object.
When large queries to a database are executed,
the application may become unresponsive for a long period of time. To avoid
this behavior and decrease the waiting time of the user, the query can be
executed on a background thread, releasing the application for other tasks
until the data is returned from the database and databinding is performed.
Requirements
The following list outlines the recommended hardware, software,
network infrastructure, and service packs that you need:
- Microsoft Visual Studio .NET
- Access to the Northwind sample database
Background
By design, Windows Form or Control methods cannot be called on a
thread other than the one that created the form or control. If you attempt to
do this, an exception is thrown. Depending on the exception handling
implemented in your code, this exception may cause your application to
terminate. If no exception handling is implemented, the following error message
is displayed:
An unhandled exception of type
'System.ArgumentException' occurred in system.windows.forms.dll
Additional information: Controls created on one thread cannot be parented to a
control on a different thread.
The exception is raised because
Windows Forms are based on a single-threaded apartment (STA) model. Windows
Forms can be created on any thread; after they are created, however, they
cannot be switched to a different thread. In addition, the Windows Form methods
cannot be accessed on another thread; this means that all method calls must be
executed on the thread that created the form or control.
Method calls
that originate outside the creation thread must be marshaled (executed) on the
creation thread. To do this asynchronously, the form has a
BeginInvoke method that forces the method to be executed on the thread that
created the form or control. The synchronous method call is done with a call to
the
Invoke method.
Build the Windows Forms application
This section describes how to create a Windows Form application
that queries a database on a background thread and uses the
BeginInvoke method to perform databinding on a
DataGrid.
- Start Visual Studio .NET.
- Create a new project, select Visual C# as the Project Type, and use the Windows Application template.
- Add a Button object to the form, and change its Text property to "Query on Thread".
- Add another Button to the form, and change its Text property to "Query on Form".
- Add a Label to the form, and clear its Text property.
- Add a TextBox to the form.
- Add a DataGrid to the form.
- Right-click the form, and then click View Code; this displays the code of your application.
- Add the following using statements to the top of the page in order to use the Threading and SqlClient namespaces:
using System.Threading;
using System.Data.SqlClient;
- Add the following code immediately below the Windows Forms
Designer Generated Code:
static Form1 MyForm;
Thread UpdateThread;
ThreadStart UpdateThreadStart = new ThreadStart(QueryDataBase);
static MethodInvoker CallDataBindToDataGrid = new MethodInvoker(DataBindToDataGrid);
static DataSet MyDataSet;
static SqlDataAdapter MyDataAdapter;
static string MyQueryString = "SELECT [Order Details].*, Orders.CustomerID, Orders.EmployeeID, Orders.OrderDate FROM [Order Details] CROSS JOIN Orders";
static SqlConnection MyConnection = new SqlConnection("data source=localhost;initial catalog=Northwind;integrated security=SSPI;");
Note The query used in this demonstration is a Cartesian Product that
returns over 165,000 rows from the Northwind database. The amount of data returned is large so that the
responsiveness of the form can be demonstrated. - Open the Windows Forms Design view.
- Double-click the Query on Thread button, and paste the following code in the Click event for this
button:
UpdateThread = new Thread(UpdateThreadStart);
UpdateThread.Name = "Update Thread";
UpdateThread.IsBackground = true;
UpdateThread.Start();
- Open the Design view again, and then double-click the Query on Form button. Paste the following code in the Click event for this
button:
QueryDataBase();
- Paste the following code below the button events that you
added in the earlier steps:
// Sub routine that is to be executed on Form's thread.
static void DataBindToDataGrid()
{
MyForm.dataGrid1.DataSource = MyDataSet;
MyForm.dataGrid1.DataMember = "MyTable";
MyDataSet = null;
MyDataAdapter = null;
}
// Sub routine used by the background thread to query database.
static void QueryDataBase()
{
MyDataSet = new DataSet();
MyConnection.Open();
MyDataAdapter = new SqlDataAdapter(MyQueryString, MyConnection);
MyForm.label1.Text = "Filling the DataSet";
MyDataAdapter.Fill(MyDataSet, "MyTable");
MyConnection.Close();
MyForm.label1.Text = "DataSet Filled";
MyForm.BeginInvoke(CallDataBindToDataGrid);
}
These Sub routines are used by the background thread to query the
database and databind it to the DataGrid located on the Windows Form when the
first button is clicked. The Click event of the second button calls the
QueryDataBase Sub routine directly, and will be executed on the Windows Form
thread. - Modify the main function to look as follows:
MyForm = new Form1();
Application.Run(MyForm);
- Press CTRL+SHIFT+B to build your application.
Demonstration
To see the benefit that is gained by using a background thread to
query the database, follow these steps:
- Press CTRL+F5 to execute your application without debugging.
- Click the Query on Form button. This begins the query on the Windows Forms thread. If you
then try to enter some text in the text box that is displayed on the form, the
application does not respond. After the query has completed (this may take some
time, depending on your computer), the DataGrid displays the results of the query.
- Click the Query on Thread button. This creates a background thread that queries the
database and keeps the application responsive to user interaction. To see this,
click the button, and then type some text in the text box on the
form.
Complete code Listing
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Threading;
using System.Data.SqlClient;
namespace DataGridThread
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
private System.Windows.Forms.DataGrid dataGrid1;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Button button2;
private System.Windows.Forms.TextBox textBox1;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
//
// TODO: Add any constructor code after InitializeComponent call
//
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.dataGrid1 = new System.Windows.Forms.DataGrid();
this.button1 = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
this.button2 = new System.Windows.Forms.Button();
this.textBox1 = new System.Windows.Forms.TextBox();
((System.ComponentModel.ISupportInitialize)(this.dataGrid1)).BeginInit();
this.SuspendLayout();
//
// dataGrid1
//
this.dataGrid1.DataMember = "";
this.dataGrid1.HeaderForeColor = System.Drawing.SystemColors.ControlText;
this.dataGrid1.Location = new System.Drawing.Point(8, 8);
this.dataGrid1.Name = "dataGrid1";
this.dataGrid1.Size = new System.Drawing.Size(736, 208);
this.dataGrid1.TabIndex = 0;
//
// button1
//
this.button1.Location = new System.Drawing.Point(16, 232);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(120, 23);
this.button1.TabIndex = 1;
this.button1.Text = "Query on Thread";
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// label1
//
this.label1.Font = new System.Drawing.Font("Microsoft Sans Serif", 15F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
this.label1.Location = new System.Drawing.Point(16, 276);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(720, 24);
this.label1.TabIndex = 2;
//
// button2
//
this.button2.Location = new System.Drawing.Point(144, 232);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(112, 23);
this.button2.TabIndex = 3;
this.button2.Text = "Query on Form";
this.button2.Click += new System.EventHandler(this.button2_Click);
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(268, 232);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(472, 20);
this.textBox1.TabIndex = 4;
this.textBox1.Text = "";
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(752, 330);
this.Controls.AddRange(new System.Windows.Forms.Control[] { this.textBox1, this.button2, this.label1, this.button1, this.dataGrid1});
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
((System.ComponentModel.ISupportInitialize)(this.dataGrid1)).EndInit();
this.ResumeLayout(false);
}
#endregion
/// <summary>
/// The main entry point for the application.
/// </summary>
///
static Form1 MyForm;
Thread UpdateThread;
ThreadStart UpdateThreadStart = new ThreadStart(QueryDataBase);
static MethodInvoker CallDataBindToDataGrid = new MethodInvoker(DataBindToDataGrid);
static DataSet MyDataSet;
static SqlDataAdapter MyDataAdapter;
static string MyQueryString = "SELECT Products.* FROM [Order Details] CROSS JOIN Products";
static SqlConnection MyConnection = new SqlConnection("data source=localhost;initial catalog=Northwind;integrated security=SSPI;");
[STAThread]
static void Main()
{
MyForm = new Form1();
Application.Run(MyForm);
}
private void Form1_Load(object sender, System.EventArgs e)
{
}
private void button1_Click(object sender, System.EventArgs e)
{
UpdateThread = new Thread(UpdateThreadStart);
UpdateThread.Name = "Update Thread";
UpdateThread.IsBackground = true;
UpdateThread.Start();
}
private void button2_Click(object sender, System.EventArgs e)
{
QueryDataBase();
}
static void DataBindToDataGrid()
{
MyForm.dataGrid1.DataSource = MyDataSet;
MyForm.dataGrid1.DataMember = "MyTable";
MyDataSet = null;
MyDataAdapter = null;
}
static void QueryDataBase()
{
MyDataSet = new DataSet();
MyConnection.Open();
MyDataAdapter = new SqlDataAdapter(MyQueryString, MyConnection);
MyForm.label1.Text = "Filling the DataSet";
MyDataAdapter.Fill(MyDataSet, "MyTable");
MyConnection.Close();
MyForm.label1.Text = "DataSet Filled";
MyForm.BeginInvoke(CallDataBindToDataGrid);
}
}
}