[ 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
Trang 1[ 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 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;
Trang 2// 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 |
Trang 3ThreadState.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);
Trang 4// 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
Trang 5foreground 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 ]