在上述范例中我们先编写了符合 AsyncCallback Delegate 类所定的函数签名(signature)的回调函数 HandleAsyncCallBack,在主线程通过以下语法转交工作线程做数据库查询时,一并告知当查询完毕获得结果后,工作线程要执行的回调函数。
.BeginExecuteReader(New AsyncCallback(AddressOf HandleAsyncCallBack), Nothing)
当工作线程取回执行结果后,会直接执行 HandleAsyncCallBack。若如上述范例需要更新画面,这通常要主线程来完成,否则会造成多条线程抢资源,而 .NET Windows Form 和控制项为了性能并未提供多线程同步的机制,多条线程同时执行相同的程序代码,将导致变量内容中的数据结构错乱。因此在回调函数中通过以下的语法将更新画面的工作交回主线程执行:
Me.Invoke(New PopulateList(AddressOf ListProducts), dRead)
上述通过回调函数执行异步的工作是最常使用的机制。接着,我们再以等待同步对象收到通知机制编写另一个程序代码范例。程序画面如图12-4 所示:
图12-4 异步同时执行并等待三个查询结果
在这个程序范例中,我们模拟同时访问不同的数据源,因为能够简单地在同一台机器上测试,所以仅同时访问 AdventureWorks 范例数据库上的三个数据表。但在真实世界中,你可能是从不同的数据库服务器中各自取得相关的记录,而有些环境执行比较快,另一些环境较没有效率,因此数据会先后取得。这时可以 WaitHandle 类的静态方法 WaitAny 将先取得的结果先处理,后返回的结果后处理。程序代码范例如列表12-4所示:
程序代码列表12-4 以 WaitHandle 对象等待执行完毕后再更新画面
Button1.Enabled = False
Dim strDelay(2) As String
Dim cmd(2) As SqlCommand
Dim hdl(2) As System.Threading.WaitHandle
Dim dgv() As DataGridView = {DataGridView1, DataGridView2, DataGridView3}
Dim ar(2) As IAsyncResult
Dim dr(2) As SqlDataReader
Dim dt(2) As System.Data.DataTable
Dim rnd As New Random
Dim i As Integer
Label1.Text = "三个查询分别等待 "
Dim strCnn As String = _
ConfigurationSettings.ConnectionStrings("AWConnectionString").ConnectionString
Using cnn As New SqlConnection(strCnn), cnn1 As New SqlConnection(strCnn), _
cnn2 As New SqlConnection(strCnn)
For i = 0 To 2
strDelay(i) = rnd.Next(1, 5).ToString()
Label1.Text &= strDelay(i).ToString() & ","
'查询时分别让 SQL Server 等待一段随机数时间,以模拟不同的执行性能
strDelay(i) = "WAITFOR DELAY '00:00:0" & strDelay(i) & "'"
cmd(i) = New SqlCommand()
dt(i) = New System.Data.DataTable()
dgv(i).DataSource = Nothing
Next i
Label1.Text = Label1.Text.Substring(0, Label1.Text.Length - 1) & " 秒。"
Me.Refresh()
With cmd(0)
.CommandText = strDelay(0) & _
" SELECT TOP 1000 CustomerID,SalesPersonID,CustomerType FROM Sales.Customer"
.Connection = cnn
cnn.Open()
'非同步执行,并取回同步对象 WaitHandle
ar(0) = .BeginExecuteReader()
hdl(0) = ar(0).AsyncWaitHandle
End With
With cmd(1)
.CommandText = strDelay(1) & _
" SELECT TOP 1000 SalesOrderID,OrderDate,CustomerID FROM Sales.SalesOrderHeader"
.Connection = cnn1
cnn1.Open()
ar(1) = .BeginExecuteReader()
hdl(1) = ar(1).AsyncWaitHandle
End With
With cmd(2)
.CommandText = strDelay(2) & _
" SELECT TOP 1000 SalesOrderID,ProductID,LineTotal FROM Sales.SalesOrderDetail"
.Connection = cnn2
cnn2.Open()
ar(2) = .BeginExecuteReader()
hdl(2) = ar(2).AsyncWaitHandle
End With
Dim iHdl As Integer
'当三个查询中有一个完成时,就让主线程先更新该 Handle 所代表的查询
For i = 0 To 2
iHdl = System.Threading.WaitHandle.WaitAny(hdl, 5000, False)
If (System.Threading.WaitHandle.WaitTimeout = iHdl) Then
'Throw New Exception("等待超过 5 秒钟都没有任何查询返回")
MessageBox.Show("等待超过 5 秒钟都没有任何查询返回")
Exit For
End If
'取回查询结果所形成的 SqlDataReader
dr(iHdl) = cmd(iHdl).EndExecuteReader(ar(iHdl))
'直接通过 SqlDataReader 将记录装载到 DataTable
dt(iHdl).Load(dr(iHdl))
dgv(iHdl).DataSource = dt(iHdl)
Label1.Text &= iHdl.ToString() & " 返回 "
'更新画面
Me.Refresh()
Next
For i = 0 To 2
If Not dr(i) Is Nothing AndAlso Not dr(i).IsClosed Then dr(i).Close()
'hdl(i).Close()
Next
End Using
Button1.Enabled = True
责任编辑:cyth