VB.NET中使用代表对方法异步调用

为什么要使用异步调用(Asynchronous Method Execution)

按照我们常规的思维方式,计算机应该是干完一件事,然后再干下一件。用术语来说,这种执行任务的方式叫做同步执行(Synchronous Execution)。既然这样,那么为什么要引入异步执行的概念呢?原因很简单,因为同步执行在有些情况下效果不理想,不能完成我们预期的目的。举两个简单的例子来说明一下这个问题。

a. 一个客户端程序(Client Side Program)要从后台数据库取回一个复杂的数据集合。可能这个数据库操作本身很费时,也可能是网络传输的数度比较慢,总之这个方法调用可能要花20秒时间。如果使用同步调用,那么在数据库结果返回之前,用户必须耐心等待,什么也不能做。这时候你可能会希望这个调用慢慢的在别处进行,程序马上返回好让你做其它的工作。等什么时候数据返回了,在进行其随后相应的操作。这种情形下,你就需要对数据库操作的方法进行异步调用。

b.一个网上机票查询订阅程序。当客户要查询从北京到芝加哥的所有机票的时候,这个程序可能要在后台通过Web Service对美国西北航空公司,中国国际航空公司和东方航空公司进行访问。将这些公司的机票情况汇总后一起以HTML的形式返回给用户。如果是同步调用,那么需要一个接一个进行Web Service调用。如果每个调用花费10秒钟的话,那么整个过程就要30秒钟。如果你使用异步调用,那么你可以在同几乎一时间就对三个公司发出相应的请求,10秒后当结果从三个不同的网站返回来后,你就可以汇总并返回各用户了。这样,整个过程只需要10秒左右。

看到这里你可能会说,这个问题没什么新鲜的。我在C++,Java里都可以用线程(Thread)来达到这样的效果。的确,大多数的高级语言都允许你创建新的线程来手工实现这样的调用。但是这些手工操作比较复杂,程序员需要自己控制线程的创建,销毁,协调等等许多细节工作,容易产生错误。并且在大型的服务器端的程序中,手工控制线程有时性能不够优化,不能根据当前具体服务器的处理器情况来动态的和智能的优化线程的数量。基于这个原因,.NET创建了一种相对简单的异步方法调用机制,使这一问题变得更加简单。这就是今天要谈的使用代表(Delegates)对方法进行异步调用。(本文以VB.NET来进行示范,C#的异步调用和此类似,就不再给出例程了)

实现异步调用的步骤和机理

假设有这样一个方法(Method),它接受一个班级的名称,然后查询数据库,返回这个班级所有同学的名单。

Class DemoClass
public shared Function GetStudentsList(ClassName as String)
as String()
'查询数据库
'其它操作
End function
End Class



如果对这样一个方法进行异步调用的话,那么你首先需要定义一个有同样方法签名(Function Signature)的代表(Delegate),比如

Delegate Function GetStudentListDelegate (ClassName as String) as String()



下一步,你需要生成一个代表实例(Instance),然后将这个代表和你的真正的方法“捆绑”起来,如

Dim delegate as GetStudentListDelegate
GetStudentListDelegate = AddressOf DemoClass.GetStudentsList



(为了简单起见,这里使用了静态方法,这其实不是必须的)

当你做到这步的时候,.NET的编译器在后台为你的代表增加了几个方法,它们是Invoke, BeginInvoke, EndInvoke.

如果你使用Invoke方法,那么其效果是同步调用,比如

delegate.Invoke("class90")



在这种情况下,代表将输入参数"class90"传递给方法GetStudentsList,然后将这个方法的返回值返回给用户。这种使用方法是同步的,不是我们所期待的。如果要达到异步效果,我们要使用BeginInvoke和EndInvoke。

让我们先看看BeginInvoke

你的使用方法可能如下所示:

Dim ar as System. IAsyncResult
ar = delegate.BeginInvoke("class90",Nothing, Nothing)



你可能会发现,这种调用方法有些不同。首先是多出两个输入参数,其次是返回值是System. IAsyncResult。这到底是怎么一回事呢?

当你调用BeginInvoke的时候,一系列的事情在后台自动发生了。


当你用代表发出调用请求后,CLR(公共语言运行环境,Common Language Runtime)接到这个请求,并将这个请求放置到一个内部的处理队列(Queue)中去。一旦放置完成后,CLR马上就给调用者返回一个IAsyncResult的对象。这个对象很重要,我们一会儿还要解释他的具体作用。


当调用者收到返回的IAsyncResult对象后,它就可以进行下一步的工作。由于将请求放置到队列中是个非常快速的操作,所以调用者马上就可以去完成下一个动作,没有被“阻挡(Block)”。


CLR在后台维持着一个“线程池(Thread Pool)”。这些线程守候着前面提到的那个处理队列。一旦有任务被放置到队列中,一个线程就会拿到这个任务并执行它。也就是说原来要调用者线程执行的费时的操作被线程池中的一个线程代劳了。(这里你可以看出,不管是用什么样的语言,在异步调用中,一定有其它的线程出现。或者是你手工创建它(如Java),或者是系统为你创建(如.NET)。那么这个“线程池”中究竟有几个线程呢?这个问题你可以不用关心。CLR会根据程序的特点以及当前的硬件条件自行决定。比如对于运行在单处理器平台上的一般的桌面程序,这个线程池可能有几个线程;而对于一个运行在4处理器服务器上的后台应用,线程池可能会有近百个线程。这样做的好处就是降低程序员的开发难度,让.NET的CLR去解决这些和用户应用逻辑无关的问题。)

既然有线程池的线程代替完成了那个方法调用(GetStudentsList),那么我们怎么知道后台的这个调用什么时候完成呢?这个方法调用返回的值(这里是一串学生名单)我们怎么拿到呢?这里我们就要用到前面提到的那个返回的IASyncResult对象了。

这个IASyncResult对象一个“收据”似的,通过它你可以查询后台调用是否完成。如果已经完成,你可以通过它来取回你想要的结果。

Dim ar as System.IASyncResult
ar = delegate.BeginInvoke("class90",Nothing, Nothing)
'*** 其它一些操作
。。。
'*** 检查后台调用状态
If (ar.IsCompleted) Then
'*** 取回异步调用方法的结果
End If



如果后台调用已经结束,那么你就可以用代表的EndInvoke来得到返回值。

Dim Students as String()
Students = delegate.EndInvoke(ar)



那么,如果你没有测试后台调用是否结束而直接使用EndInvoke,那后果会怎么样呢?如果后台调用没有完成,EndInvoke调用就会被“阻挡”,直到后台调用完成后才返回。如果后台调用出现异常,那么EndInvoke还可以捕捉到这个异常

Dim Students as String()
Try Students = delegate.EndInvoke(ar)
Catch ex as Exception
'处理这个异常
End Try



既然EndInvoke调用就会被“阻挡”(如果后台异步调用还没有完成),那么下面这种标较复杂情况CLR是怎样处理的呢?

Dim ar1, ar2 as System.IASyncResult
Dim rt1, rt2 as String()
ar1 = delegate1.BeginInvoke("class90",Nothing, Nothing)
ar2 = delegate2.BeginInvoke("class94",Nothing, Nothing)
rt1 = delegate1.EndInvoke(ar1)
rt2 = delegate2.EndInvoke(ar2)



在这个例子中,delegate1的调用和delegate2的调用完成顺序可能会有多种情况。比如delegate2的调用后发先至,那么EndInvoke的使用顺序是不是很重要呢?事实上,你可以忽略这个问题,CLR会保证在两个异步调用都结束后,你才可以进行下面的操作。至于它是怎么实现的,你可以不去管它。

事实上,EndInvoke是非常重要的。如果你使用了BeginInvoke,那你最好使用EndInvoke。因为你如果不使用EndInvoke,那么后台调用的异常就没有机会被捕捉到。另外,使用了EndInvoke可以让CLR释放异步调用中所使用的资源,否则你的应用程序就可能出现资源泄漏(Resource Leak)。

到这里,情况已经比较清楚了。使用Delegate可以让后台线程代替当前线程去完成费时的操作,从而使当前线程不被“阻挡”,可以马上进行其它的工作。但是,如果当前线程通过EndInvoke来得到异步调用的结果,它又很可能被“阻挡”。看起来有点“拆了东墙补西墙”的样子,好像我们没有得到什么好处。打个比方来说吧,你要到复印室去复印一批材料,这个工作要费时一个多小时。同步调用就意味着你自己亲自去复印,一个多小时候再返回办公室作其它工作。异步调用意味着你可以把复印材料交到复印室,那里有专人负责复印。你放下材料后就可以回到办公室去干其它工作了。但问题是,你要不停的查看材料是否复印好了,一旦发现复印完毕后,就马上取回作相应的操作。你不停的查看(调用代表的IsComplete方法)或者是“干等”(调用代表的EndInvoke方法)实际上还是把你“捆住”了,你没有能腾出手来干其它的事。能不能我把材料放到复印室就不管了,等复印好后他们给我把材料送回来?。答案是可以的,那就是利用回调函数(Callback Function)。

还记得我们前面的那个例子吗,我们用代表调用BeginInvoke的时候,多了两个参数,其中一个就是回调函数,另外一个是执行回调函数的参数。回调函数的意思是在后台线执行完异步调用的方法后,自动去执行的函数(或方法)。在执行这个回调函数的时候,你还可以指定参数。也是就说,你让复印室的复印员完成复印后,把材料给你放回到你的办公桌上,并且每10页一摞。这个“放到办公桌上”就是回调函数,而“每10页一摞”就是回调函数执行时使用的参数。

'回调函数的参数
Dim myValue as Integer = 10
'回调函数的定义
Sub PutToDesk(Byval ar as IAsyncResult)
dim x as Integer = CInt(ar.AsyncState)'拿到参数
'相应的操作
End Sub
'使用回调函数的方法
Private CallBackDelegate as AsyncCallBack = AddressOf PutToDesk
...
Dim ar as System.IASyncResult
ar = delegate.BeginInvoke("class90",CallBackDelegate, myValue)



在使用回调函数时要注意,你的回调函数必须和.NET系统定义的AsyncCallBack一起使用,即你的回调函数必须和AsyncCallBack具有一样的签名。也就是说它必须是子程序(Sub Procedure),必须有一个IAsyncResult类对象为输入参数。

要注意的是回调函数是由后台线程来执行的(就是我们所说的复印员)。这种执行方法在有些情况下会造成不小的问题。比如说,在Windows的桌面应用中有这样一个规则,那就是一切用户界面元素的更改(外观以及属性)必须由这些界面元素的创建线程来进行(术语上叫界面主线程,Primary UI Thread)。如果其它线程试图更新界面元素,那么将会有不可预测的后果。如果你违反了这一原则,那么你的程序在理论上讲是不安全的,即使是问题你一时还没有发现。

就上面一个例子而言,如果后台线程从数据库里拿到了学生名单,那么很可能它要执行的回调函数就是更新界面上的一个下拉式列表(Dropdown List),或是一个表格(Grid)什么的。但是这样做又违反了我们所说的界面更新的线程原则。那么我们该怎么办呢?

其实这个问题并不难解决,设计师在设计.NET的时候已经考虑到了这个问题。具体的解决办法我将在下篇文章中做出介绍。

转载于:https://www.cnblogs.com/aowind/archive/2006/06/21/SynchronousExecution.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: BeginInvoke 方法是在 .NET Framework 的 Visual Basic (VB.NET) 用于异步执行委托的方法。它允许您在不阻塞 UI 线程的情况下异步执行长时间运行的操作。下面是使用 BeginInvoke 的示例代码: ``` Private Delegate Sub UpdateTextDelegate(ByVal message As String) Private Sub UpdateText(ByVal message As String) TextBox1.Text = message End Sub Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Dim updateTextDelegate As New UpdateTextDelegate(AddressOf UpdateText) updateTextDelegate.BeginInvoke("Hello World", Nothing, Nothing) End Sub ``` 上面的代码创建了一个 UpdateTextDelegate 类型的委托,该委托可以接受一个字符串参数。在按钮单击事件处理程序,我们创建了一个 UpdateTextDelegate 的实例,并将 UpdateText 函数的地址分配给它。最后,我们使用 BeginInvoke 方法异步调用 UpdateTextDelegate,从而在不阻塞 UI 线程的情况下更新 TextBox 的内容。 ### 回答2: BeginInvoke 是一个 VB.NET 方法,用于在多线程应用程序异步执行一个代理方法使用 BeginInvoke 方法时,首先需要创建一个代理方法(Delegate),该代理方法参数和返回值需要与要执行的方法相匹配。然后,可以通过创建该委托类型的一个实例来调用 BeginInvoke 方法。 以下是 VB.NET 使用 BeginInvoke 方法的一个示例: ```vb.net Imports System.Threading Public Class Form1 Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load ' 创建一个委托类型的实例 Dim myDelegate As New MyDelegate(AddressOf DoWork) ' 异步执行代理方法 myDelegate.BeginInvoke(100, New AsyncCallback(AddressOf Callback), Nothing) End Sub ' 创建一个委托,该委托和要执行的方法的参数和返回值相匹配 Delegate Sub MyDelegate(ByVal value As Integer) ' 要执行的方法 Private Sub DoWork(ByVal value As Integer) ' 模拟耗时的任务 Thread.Sleep(500) ' 执行完任务后更新 UI Me.Invoke(Sub() Me.Text = value.ToString()) End Sub ' 回调方法,在异步任务完成后执行 Private Sub Callback(ByVal ar As IAsyncResult) ' 执行完回调后更新 UI Me.Invoke(Sub() Me.Text = "任务完成") End Sub End Class ``` 在上述示例,首先创建了一个委托类型的实例 `myDelegate`,并将要执行的方法 `DoWork` 与该委托实例关联。然后,通过调用 `myDelegate.BeginInvoke` 方法来异步执行该方法。在 `BeginInvoke` 方法传入了要执行的方法的参数 `100`,以及一个回调方法 `Callback`(该方法在异步任务完成后执行)。在回调方法,通过调用 `Me.Invoke` 方法来更新 UI。 通过使用 BeginInvoke 方法,可以在多线程应用程序实现异步执行任务,并且能够在任务完成后更新 UI,提升用户体验。 ### 回答3: VB.NET 的 BeginInvoke 方法是一种使用委托异步调用函数的方式。当我们需要在后台线程执行某个函数时,可以使用 BeginInvoke 方法来实现。 使用 BeginInvoke 方法,首先需要定义一个委托类型,该委托类型需要和要执行的函数具有相同的签名。例如,如果要执行的函数是一个没有参数和返回值的方法,那么定义的委托类型也应该是这样的。 接下来,我们需要实例化委托,并传入要执行的函数作为参数。然后,调用该委托的 BeginInvoke 方法,将函数的其他参数传递给该方法。 BeginInvoke 方法将函数的执行异步化,程序会立即返回并继续执行后面的代码,而不会等待函数的执行结果。如果我们希望获取函数的执行结果,可以使用委托的 EndInvoke 方法。 在使用 BeginInvoke 方法时,需要注意以下几点: 1. 使用 BeginInvoke 方法后,最好使用 EndInvoke 方法来等待函数执行完成,否则可能会导致程序不正常终止。 2. 使用 BeginInvoke 方法时,需要确保在多线程环境下的线程安全性,例如使用互斥锁或其他同步机制来保护共享资源的访问。 3. BeginInvoke 方法可以用于执行长时间运行的函数,以避免阻塞主线程,保持界面的响应性。 总之,VB.NET 的 BeginInvoke 方法是一种实现函数异步调用的方式,可以用于在后台线程执行某个函数,提高程序的性能和响应性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值