[ Team LiB ] |
Recipe 9.2 Canceling an Asynchronous QueryProblemGiven 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. SolutionAbort the background thread and clean up in an exception handler. The sample code contains two event handlers and a single method:
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; } } DiscussionRecipe 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.
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:
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 ] |