到目前为止,我们讨论的调用技术都是简单地改变执行过程,从某个方法到另一个方法。我们还进一步希望将执行过程分成两个分支,允许其中一个分支执行一个给定方法的指令.而剩下的分支独立地继续执行其正常的过程。图6.11表明了这个概念。在一个多处理程序的机器上,两个分支实际上可以并行地执行。在一个单处理程序机器上,CLR将在共享的CPU上抢占式地(preemptively)调度两个执行分支。
分叉执行的主要目的是:当程序的一部分被阻塞时(例如,等待I/O完成或用户输入指令),处理过程得以继续进行。分叉执行由于其并行性,还能增加多CPU的吞吐量(throughput),然而,这需要一个非常谨慎的设计风格,以避免对于共享资源的过度争用(comention)。
用于分叉指令流的主要机制是使用异步方法调用。异步方法调用将执行分成两个流。新的指令流执行目标方法体。原来的指令流继续其正常处理。
CLR通过使用一个工作队列(work queue)实现异步方法凋用。当异步调用一个方法时,CLR将方法参数和目标方法的地址打包到一个请求消息中, 接着,CLR将这条消息插入到一个进程范围的工作队列中.CLR维护一个操作系统级别的线程池,用于监听这个工作队列,当队列上的请求到达时,CLR从线程池中分发一个线程来执行工作。就异步方法调用的情形来说,这个工作只是简单地调用目标方法。
我们总是通过委托对象执行异步方法调用。回想一下委托类型,它有两个由编译器产生的方法,Invoke和构造函数。委托类型还有另外两个方法用于执行异步方法调用,Beginlnvoke方法和EndInvoke方法。像Invoke一样,这两个方法都必须被标记为runtime,因为CLR将在运行时为它们提供基于其签名的实现。
CLR使用BeginInvoke方法发出一个异步方法请求。BeginInvoke方法的CLR合成的(CLR-synthesized)实现只是简单地创建了一个包含参数的工作请求,并且将该请求插入到工作队列中。BeginInvoke通常于目标方法在线程地上开始执行之前返回,但由于底层线程凋度的不可预测性,有可能(虽然可能性比较小)在调用线程从BeginInvoke返回之前,目标方法就已经执行完毕了。
BeginInvoke的签名与Invoke的签名是相似的,考虑下面的c#委托类型定义:
Public delegate double
Add(double x,double y,out double z,ref bool overflow)
这个委托类型将可能有这样的Invoke方法签名
public double Invoke(double x, double y,out double z, ref boll overflow);
对应的BeginInvoke方法则是这样的:
Public System.IASyncResult
BeginInvoke(double z,double y,out double z,ref bool overflow,System.AsyncCallback
complete,object state);
注意,BeginInvok方法的签名有两点不同:一个就是BeginInvoke接收两个额外的参数,用于设定调用的处理方式。这两个参数在本节后面即将讨论到;另一个就是BeginInvoke总会返回一个调用对象的引用。这个调用对象表示该方法执行期间的状态,并且能够用于在过程中控制和检测调用。调用对象总是实现System.IAsyncResult
接口。
如示例6.11所示,IAsyncResult接口有四个成员。Completed Synchronously属性表明在BeginInvoke期间执行会不会发生。尽管CLR的异步调用管道从不这么做,但实现异步调用方法的对象可能会同步处理一个异步请求。
IASyncResult.IsCompleted属性表明该方法是否已经执行完毕。这允许调用方轮询(poll)调用对象,以确定调用实际是否已经完成执行。
Public System.IasyncResult
BeginInvoke(double x,double y,out double z,ref bool overflow,System.AsyncCallback
complete,object state);
Static void f(Add add){
Bool overflow=true;double z;
//发出调用
IasyncResult ar=add.BeginInvoke(3,4,out z,ref overflow,null,null);
//轮询,直到调用完成
While(!ar.IsCompleted)
System.Threading.Thread.Sleep(1);
}
作为查询方式的一个选择,AsyncWaitHandle属性返回一个System.Threading.WaitHandle对象,这样,通过线程同步技术可以将它用于等待。
Static void f(Add add){
//发出调用
IAsyncResult ar=add.BeginInvoke(3,4,out z,ref overflow,null,null);
//休眠等待,直到调用完成
Ar.AsyncWaitHandle.WaitOne();
}
这个变体被认为更有效率,因为调用方的底层操作系统线程将被置为睡眠状志,直到调用完成。这对系统中的其他线程来说,增加了它们对CPU更多的利用机会。
最后,你可以使用Begininvoke签名的最后一个参数,这允许调用方将一个任意对象与该方法调用相关联。然后通过调用对象的AsyncState属性使得该用户提供的(user-provided)对象可用。当你在发行方法(issuing method) 范围之外使用调用对象时,这个实用部件特别有用,原因是它允许调用方向那些最终将处理调用的完成代码提供额处的信息。//增加我们的属性到新的上下文
ctor.ContextProperties.Add(new BoostProperty());
//在调用上下文中保存当前线程的优先级
ctor.LogicalCallContext.Set