某些数据库操作(例如命令执行)可能要花费很长时间才能完成。在此类情况下,单线程应用程序必须阻塞其他操作,并且等待该命令完成,然后才可以继续执行它们自己的操作。比较而言,如果能够将长时间运行的操作分配给某个后台线程,就可以允许前台线程在整个操作期间保持活动状态。例如,在 Windows 应用程序中,通过将长时间运行的操作委托给后台线程,可允许用户界面线程在操作执行时保持响应状态。
.NET Framework 提供若干标准异步设计模式,开发人员可以通过这些模式充分利用后台线程并释放用户界面线程或高优先级的线程以完成其他操作。ADO.NET 在其 SqlCommand 类中支持相同的设计模式。具体而言,BeginExecuteNonQuery、BeginExecuteReader 和 BeginExecuteXmlReader 方法(与 EndExecuteNonQuery、EndExecuteReader 和 EndExecuteXmlReader 方法搭配)提供异步支持。
在您的应用程序一次只处理一个异步操作时,用于处理异步操作的回调和轮询模型十分有用。等待模型提供了一种更灵活的方式来处理多个异步操作。有两种等待模型,是根据用于实现它们的 WaitHandle 方法命名的:等待(任何)模型和等待(所有)模型。
要使用上述任一等待模型,您需要使用 BeginExecuteNonQuery、BeginExecuteReader 或 BeginExecuteXmlReader 方法返回的 IAsyncResult 对象的 AsyncWaitHandle 属性。WaitAny 和 WaitAll 方法都要求您将多个 WaitHandle 对象一起组合在一个数组中,作为一个参数发送。
这两种等待方法都监控异步操作,等待操作完成。WaitAny 方法等待任何操作完成或超时。一旦您知道某一特定操作完成后,就可以处理其结果,然后继续等待下一个操作完成或超时。WaitAll 方法等待 WaitHandle 实例数组中的所有进程都完成或超时后,再继续。
当您需要在不同服务器上运行时间长度不等的多个操作时,或者在服务器的性能强大到足以同时处理所有查询时,等待模型的优点可以最大地发挥。在此处提供的示例中,通过将长度不等的多个 WAITFOR 命令添加到不重要的 SELECT 查询,三个查询模拟长进程。
下面的示例说明等待(任何)模型。一旦启动了三个异步进程后,就调用 WaitAny 方法以等待其中任何一个进程完成。在每个进程完成后,调用 EndExecuteReader 方法并读取结果 SqlDataReader 对象。在此时,实际的应用程序将很可能使用 SqlDataReader 填充该页的某个部分。在这个简单的示例中,进程完成时间被添加到与该进程相对应的文本框中。合起来看,文本框中的这些时间说明以下结论:每次进程完成后都执行代码。
要设置此示例,请创建一个新的 ASP.NET 网站项目。将一个 Button 控件和四个 TextBox 控件放置于页面上(接受每个控件的默认名称)。
在窗体的类中添加以下代码,根据环境的需要修改连接字符串。
[C#]
// Add the following using statements, if they are not already there.using System;using System.Data;using System.Configuration;using System.Web;using System.Web.Security;using System.Web.UI;using System.Web.UI.WebControls;using System.Web.UI.WebControls.WebParts;using System.Web.UI.HtmlControls;using System.Threading;using System.Data.SqlClient;// Add this code to the page's classstring GetConnectionString() // To avoid storing the connection string in your code, // you can retrieve it from a configuration file. // If you have not included "Asynchronous Processing=true" // in the connection string, the command will not be able // to execute asynchronously.{ return "Data Source=(local);Integrated Security=SSPI;" + "Initial Catalog=AdventureWorks;" + "Asynchronous Processing=true";}void Button1_Click(object sender, System.EventArgs e){ // In a real-world application, you might be connecting to // three different servers or databases. For the example, // we connect to only one. SqlConnection connection1 = new SqlConnection(GetConnectionString()); SqlConnection connection2 = new SqlConnection(GetConnectionString()); SqlConnection connection3 = new SqlConnection(GetConnectionString()); // To keep the example simple, all three asynchronous // processes select a row from the same table. WAITFOR // commands are used to emulate long-running processes // that complete after different periods of time. string commandText1 = "WAITFOR DELAY '0:0:01';" + "SELECT * FROM Production.Product " + "WHERE ProductNumber = 'BL-2036'"; string commandText2 = "WAITFOR DELAY '0:0:05';" + "SELECT * FROM Production.Product " + "WHERE ProductNumber = 'BL-2036'"; string commandText3 = "WAITFOR DELAY '0:0:10';" + "SELECT * FROM Production.Product " + "WHERE ProductNumber = 'BL-2036'"; try // For each process, open a connection and begin // execution. Use the IAsyncResult object returned by // BeginExecuteReader to add a WaitHandle for the // process to the array. { connection1.Open(); SqlCommand command1 = new SqlCommand(commandText1, connection1); IAsyncResult result1 = command1.BeginExecuteReader(); WaitHandle waitHandle1 = result1.AsyncWaitHandle; connection2.Open(); SqlCommand command2 = new SqlCommand(commandText2, connection2); IAsyncResult result2 = command2.BeginExecuteReader(); WaitHandle waitHandle2 = result2.AsyncWaitHandle; connection3.Open(); SqlCommand command3 = new SqlCommand(commandText3, connection3); IAsyncResult result3 = command3.BeginExecuteReader(); WaitHandle waitHandle3 = result3.AsyncWaitHandle; WaitHandle[] waitHandles = { waitHandle1, waitHandle2, waitHandle3 }; int index; for (int countWaits = 0; countWaits <= 2; countWaits++) { // WaitAny waits for any of the processes to // complete. The return value is either the index // of the array element whose process just // completed, or the WaitTimeout value. index = WaitHandle.WaitAny(waitHandles, 60000, false); // This example doesn't actually do anything with // the data returned by the processes, but the // code opens readers for each just to demonstrate // the concept. // Instead of using the returned data to fill the // controls on the page, the example adds the time // the process was completed to the corresponding // text box. switch (index) { case 0: SqlDataReader reader1; reader1 = command1.EndExecuteReader(result1); if (reader1.Read()) { TextBox1.Text = "Completed " + System.DateTime.Now.ToLongTimeString(); } reader1.Close(); break; case 1: SqlDataReader reader2; reader2 = command2.EndExecuteReader(result2); if (reader2.Read()) { TextBox2.Text = "Completed " + System.DateTime.Now.ToLongTimeString(); } reader2.Close(); break; case 2: SqlDataReader reader3; reader3 = command3.EndExecuteReader(result3); if (reader3.Read()) { TextBox3.Text = "Completed " + System.DateTime.Now.ToLongTimeString(); } reader3.Close(); break; case WaitHandle.WaitTimeout: throw new Exception("Timeout"); break; } } } catch (Exception ex) { TextBox4.Text = ex.ToString(); } connection1.Close(); connection2.Close(); connection3.Close();} |