12.2异步执行命令
来源:优易学  2010-1-12 12:03:06   【优易学:中国教育考试门户网】   资料下载   IT书店

 

一般来说文件 I/O、网络访问乃至于 Web Services 访问,以及本节所讨论的 DB 访问等都较为耗时,.NET Framework 为这一类的类都提供了上述以 Begin~/End~ 开头的非同步执行方法,而这些方法皆成对出现。当然,也有可能是自己编写的函数其商业逻辑非常复杂,导致调用该函数后,需要等待一段时间来完成,这时还可以通过 .NET Framework 所提供的委托(Delegate)类来创建异步运行。

而所谓的异步运行,其工作方式如图12-2 所示:

图12-2 非同步运行的工作模型

在以往我们都是自己创建工作线程,让耗时的工作以其他的线程来执行,主线程依然与用户交互。但 .NET Framework 通过异步工作模型,将多线程程序设计与编写的复杂度隐藏起来,只要套用这个模型,则图12-2 右边的部分 .NET Framework 会在下层删除,我们从程序代码的表面上看不出多线程的痕迹。同时,.NET Framework 在创建工作线程时,会通过线程池,所以也可以采用较有效率的多线程使用方式。

12.2.3 异步运行如何再度同步的设计模型

当主线程创建工作线程,异步地展开另外一项工作后,通常会需要参照该工作线程的执行结果。而主线程如何取得工作线程当前执行的状况,或是接收工作结果?.NET Framework 的异步运行模型提供四种让主线程和工作线程同步的方式:

回调函数(callback function):在一开始调用 Begin~ 函数时,当传入非同步执行结束后,可以调用函数所形成的委托实例。当工作线程完成所赋予的工作后,通过委托回调你所指定的回调函数。

这是最常用的方式,适合一般 Windows Form 事件驱动的工作模式,执行的步骤过程我们在之后的范例程序将会解释。

轮询(poll):通过 Begin~ 函数返回实现 IAsyncResult 接口的对象,当需要知道工作线程是否执行结束时,就检查该界面的 IsCompleted 属性,若是 true,表示工作线程执行完毕,反之,工作线程还在继续执行。

调用 End~ 函数:这会中断当前的线程,等到异步的工作做完才能继续执行。当主线程交付工作线程某项工作后,主线程继续自身的工作,但当所有可做的事情都已经做完后,必须要有工作线程的执行结果才能继续下去。可以调用 End~ 函数,一旦调用了这个函数,主线程就被迫停下来,直到异步工作完成为止。

等待同步对象收到通知:IAsyncResult 接口的 AsyncWaitHandle 属性会返回 WaitHandle 同步对象,你可以通过这个同步对象要求主线程等待通知,当工作线程执行完毕后,就会通知这个 WaitHandle 同步对象,所有等待的线程就可以继续执行下去。此种方式的效果与前一种调用 End~ 函数相似。

但如果有多个异步工作同时执行,则可以通过 System.Threading.WaitHandle.WaitAll 或 WaitAny 等方法来等待多个异步执行的 WaitHandle 对象。

若以我和你一起工作来比喻两条线程的协作,通过回调函数的方式,我把我的手机号码给你,当你做完事后打电话告诉我,而电话号码就可以比作回调函数。而轮询是你给我你的手机号码,我会经常打电话给你,问你做完没有。调用 End~ 函数则是我做我的,你做你的,当我做完后停下来一直等你做完我才能再继续做。等待同步对象收到通知与前述调用 End~ 函数的方式相似,但可以通过 WaitAll 等待多个人都完成工作,我才继续做,或者是通过 WaitAny 在多个人同时做时,只要有一个人做完,我就先接着处理该人的工作,而又有别人做完时,我再接手第二个人的工作,依此类推完成所有人的工作。

我们就上述四种取得执行结果的方式分别列举通过回调函数等待同步对象收到通知两种机制为例,各提供一个小范例程序说明如下。首先是以回调函数来取得执行结果,其所设计的范例程序界面如图12-3所示:

图12-3 等待异步访问数据的结果时,更新用户画面的内容

在这个范例中,故意访问一个耗时的存储过程,若以同步的方式执行,一旦调用该存储过程后,整个应用程序开始等待 SQL Server 返回结果,因此画面就静止不动了。而我们采用异步的方式来调用该存储过程,由于主线程还是在负责用户界面,因此画面仍可以通过 Timer 所触发的事件来更新程序进行的状态。

实际执行异步查询的范例时,我们先在服务器创建如程序代码列表12-2的存储过程:


程序代码列表12-2 创建会停滞执行数秒钟的存储过程

USE AdventureWorks

GO

CREATE PROC spGetProduct @Delay NVARCHAR(2)

AS

DECLARE @str nvarchar(10)

SET @str='00:00:' + @Delay

--设置返回结果前要等待的秒数

WAITFOR DELAY @str

SELECT Name FROM Production.Product

在上述范例中我们通过 WAITFOR DELAY 语法让 SQL Server 端的存储过程执行停滞一段时间后才返回结果,以此模拟数据库服务器执行耗时命令的状况,前端程序若不以非同步的方式执行查询,将会让用户界面显示如死机一般的情境。程序代码范例如列表2-3:

程序代码列表12-3 通过异步方式执行查询语法

Private cmd As SqlCommand

Dim conn As SqlConnection

Delegate Sub PopulateList(ByVal dReader As SqlDataReader)

Private Sub AsyncForm_Load(ByVal sender As Object, ByVal e As System.EventArgs) _

Handles Me.Load

conn = New SqlConnection(ConfigurationSettings.ConnectionStrings("AWConnectionString").ConnectionString)

End Sub

Private Sub getDataButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _

Handles getDataButton.Click

'利用 command 对象的 BeginExecuteReader 方法非同步地访问 DataReader

productListBox.Items.Clear()

cmd = New SqlCommand("spGetProduct", conn)

conn.Open()

statusLabel.Text = "Connected"

With cmd

.CommandType = CommandType.StoredProcedure

.Parameters.Add(New SqlParameter("@Delay", SqlDbType.NVarChar, 2))

.Parameters(0).Value = TextBox1.Text

.BeginExecuteReader(New AsyncCallback(AddressOf HandleAsyncCallBack),Nothing)

End With

'因为是异步,所以主线程可以继续执行以下的程序代码

ProgressBar1.Maximum = Integer.Parse(TextBox1.Text)

ProgressBar1.Value = 0

Timer1.Enabled = True

statusLabel.Text = "正在取数据"

End Sub

Public Sub HandleAsyncCallBack(ByVal result As IAsyncResult)

'异步回调函数,工作线程等到结果后,会执行这个函数

Dim dRead As SqlDataReader

Try

‘取回执行 SQL 的查询结果

dRead = cmd.EndExecuteReader(result)

'将结果交还主线程更新用户画面

Me.Invoke(New PopulateList(AddressOf ListProducts), dRead)

Catch ex As Exception

MessageBox.Show(ex.Message)

End Try

End Sub

Public Sub ListProducts(ByVal dReader As SqlDataReader)

'主线程用来更新画面的函数

Timer1.Enabled = False

ProgressBar1.Value = ProgressBar1.Maximum

While dReader.Read

productListBox.Items.Add(dReader("Name"))

End While

dReader.Close()

conn.Close()

statusLabel.Text = "断掉连线"

End Sub

Private Sub Timer1_Elapsed(ByVal sender As System.Object, _

ByVal e As System.Timers.ElapsedEventArgs) Handles Timer1.Elapsed

'通过 Timer 定时更新工作进度状态

If ProgressBar1.Value < ProgressBar1.Maximum _

Then ProgressBar1.Value += 1

End Sub Private Sub Timer1_Elapsed(ByVal sender As System.Object, _

 

上一页  [1] [2] [3] [4] 下一页

责任编辑:cyth

文章搜索:
 相关文章
热点资讯
热门课程培训