[ Team LiB ]
Recipe 9.2 Canceling anAsynchronousQuery
Problem
Given a query running that runs asynchronously on a background thread, you want to
give the user the option to cancel the query if it is taking too long.
Solution
Abort the background thread and clean up in an exception handler.
The sample code contains two event handlers and a single method:
Start Button.Click
Checks whether there is an existing background thread loading the DataSet. If the
DataSet is not being loaded, a new thread is created invoking the
AsyncFillDataSet( ) method to fill a DataSet. Otherwise, a message is displayed
stating that the DataSet is currently being filled.
Cancel Button.Click
Aborts the background thread filling the DataSet.
AsyncFillDataSet( )
This method loads a DataSet with the Orders and Order Details tables from the
Northwind database. The method displays a message when the method has started
and when it has completed. The method also traps the ThreadAbortException to
handle the situation where the fill on the background thread is canceled.
The C# code is shown in Example 9-2
.
Example 9-2. File: AsynchronousFillCancelForm.cs
// Namespaces, variables, and constants
using System;
using System.Configuration;
using System.Threading;
using System.Data;
using System.Data.SqlClient;
// Table name constants
private const String ORDERS_TABLE = "Orders";
private const String ORDERDETAILS_TABLE = "OrderDetails";
// Relation name constants
private const String ORDERS_ORDERDETAILS_RELATION =
"Orders_OrderDetails_Relation";
// Field name constants
private const String ORDERID_FIELD = "OrderID";
private const String ORDERDATE_FIELD = "OrderDate";
private Thread thread;
// . . .
private void startButton_Click(object sender, System.EventArgs e)
{
// Check if a new thread can be created.
if (thread == null ||
(thread.ThreadState & (ThreadState.Unstarted |
ThreadState.Background)) == 0)
{
// Create and start a new thread to fill the DataSet.
thread = new Thread(new ThreadStart(AsyncFillDataSet));
thread.IsBackground = true;
thread.Start( );
}
else
{
// DataSet already being filled. Display a message.
statusTextBox.Text += "DataSet still filling . . . " +
Environment.NewLine;
statusTextBox.Refresh( );
}
}
private void cancelButton_Click(object sender, System.EventArgs e)
{
// Check if the thread is running and an abort has not been requested.
if (thread != null &&
(thread.ThreadState &
(ThreadState.Stopped | ThreadState.Aborted |
ThreadState.Unstarted | ThreadState.AbortRequested)) == 0)
{
try
{
// Abort the thread.
statusTextBox.Text += "Stopping thread . . . " +
Environment.NewLine;
statusTextBox.Refresh( );
thread.Abort( );
thread.Join( );
statusTextBox.Text += "Thread stopped." +
Environment.NewLine;
}
catch (Exception ex)
{
statusTextBox.Text += ex.Message + Environment.NewLine;
}
}
else
{
statusTextBox.Text += "Nothing to stop." + Environment.NewLine;
}
}
private void AsyncFillDataSet( )
{
try
{
statusTextBox.Text = "Filling DataSet . . . " +
Environment.NewLine;
statusTextBox.Refresh( );
DataSet ds = new DataSet("Source");
SqlDataAdapter da;
// Fill the Order table and add it to the DataSet.
da = new SqlDataAdapter("SELECT * FROM Orders",
ConfigurationSettings.AppSettings["Sql_ConnectString"]);
DataTable orderTable = new DataTable(ORDERS_TABLE);
da.FillSchema(orderTable, SchemaType.Source);
da.Fill(orderTable);
ds.Tables.Add(orderTable);
// Fill the OrderDetails table and add it to the DataSet.
da = new SqlDataAdapter("SELECT * FROM [Order Details]",
ConfigurationSettings.AppSettings["Sql_ConnectString"]);
DataTable orderDetailTable = new DataTable(ORDERDETAILS_TABLE);
da.FillSchema(orderDetailTable, SchemaType.Source);
da.Fill(orderDetailTable);
ds.Tables.Add(orderDetailTable);
// Create a relation between the tables.
ds.Relations.Add(ORDERS_ORDERDETAILS_RELATION,
ds.Tables[ORDERS_TABLE].Columns[ORDERID_FIELD],
ds.Tables[ORDERDETAILS_TABLE].Columns[ORDERID_FIELD],
true);
statusTextBox.Text += "DataSet fill complete." +
Environment.NewLine;
}
catch (ThreadAbortException ex)
{
// Exception indicating that thread has been aborted
statusTextBox.Text += "AsyncFillDataSet( ): " + ex.Message +
Environment.NewLine;
}
}
Discussion
Recipe 9.1
discusses using a background thread to fill a DataSet to improve application
performance.
The ThreadState of a thread specifies its execution state. This value is a bitwise
combination of ThreadState enumeration described in Table 9-1
.
Table 9-1. ThreadState enumeration
Value Description
Aborted The thread is stopped.
AbortRequested
The Abort( ) method of the thread has been called but the thread
has not yet received the ThreadAbortException that will terminate
it.
Background The thread is being executed on a background thread rather than a
foreground thread. This is specified by the IsBackground property
of the thread.
Running
The thread has been started, is not blocked, and there is no pending
ThreadAbortException.
Stopped The thread is stopped.
StopRequested
The thread is being requested to stop. This value is for internal use
only.
Suspended The thread is suspended.
SuspendRequested The thread is being requested to suspend.
Unstarted
The thread is not started and the Start( ) method has not been called
on the thread.
WaitSleepJoin The thread is blocked by a Wait( ), Sleep( ), or Join( ) method.
In the solution, a background thread is used to fill the DataSet. The ThreadState of the
thread object is used to determine whether it can be started or whether it can be aborted,
as follows:
• A thread can be started only if it does not have a ThreadState of Unstarted or
Background.
• A thread can be aborted only if its ThreadState is not Stopped, Aborted, Unstarted,
or AbortRequested.
The Abort( ) method of the Thread raises a ThreadAbortException in the thread on which
it is invoked and begins the process of terminating the thread. ThreadAbortException is a
special exception, although it can be caught, it is automatically raised again at the end of
a catch block. All finally blocks are executed before killing the thread. Because the thread
can do an unbounded computation in the finally blocks, the Join( ) method of the
thread—a blocking call that does not return until the thread actually stops executing—is
used to guarantee that the thread has terminated. Once the thread is stopped, it cannot be
restarted.
[ Team LiB ]
. ]
Recipe 9.2 Canceling an Asynchronous Query
Problem
Given a query running that runs asynchronously on a background thread, you want to
give the. user the option to cancel the query if it is taking too long.
Solution
Abort the background thread and clean up in an exception handler.
The sample