您的位置:首页 > 大数据 > 人工智能

Visual Studio Async CTP的实现原理浅析 - 如何不使用async和await关键字来实现Async

2011-03-31 22:20 1051 查看
AsyncCTP为我们在单线程实现异步操作开辟了一条大道,尤其对于SL中的WCF来说让我们从繁琐的事件处理中解脱出来,本来我想写一个SL中使用Socket实现的RPC框架(正在我项目中使用)的系列笔记,不过有朋友提到了应该用AsyncCTP来规避WCF的异步方法带来的繁琐,事实上AsyncCTP在我项目中还不够灵活(应该是WCF对我的应用来说不够灵活),不过还是先放下那个系列,先看看AsyncCTP可以做些什么。

本文不会介绍AsyncCTP的使用方法,只分析在不改动SLCLR的基础上编译器如何根据async和await两个关键字对我们的代码做出正确的改动以达到单线程异步操作。

关于单线程异步可以参考ProgramminguserinterfacesusingF#workflows

要理解AsyncCTP强力推荐此文AsynchronousProgramminginC#usingIterators

先来看看AsyncCtpLibrary_Silverlight.dll中的几个重要的类,在System.Runtime.CompilerServices命名空间中找出这几个类AsyncMehodBuilder,AsyncMethodBuilder<TResult>,VoidAsyncMethodBuilder,TaskAWaiter,TaskAwaiter<TResult>,以及System.Threading.Tasks下面的Task,Task<TResult>看到名字基本可以知道是干啥的了,简单介绍一下:

AsyncMethodBuilder:假如async被施加在一个Task对象之前,编译器使用这个类。

AsyncMethodBuilder<TResult>:假如async被施加在Task<TResult>之前,编译器使用这个类。

VoidAsyncMethodBuilder:async被施加在一个方法声明之前,编译器使用这个类。(因此只有返回值是void的方法能加async关键字)

TaskAwaiter:对应在Task对象之前的await关键字。

TaskAwaiter<TResult>:对应在Task<TResult>对象之前的await关键字。

Task、Task<TResult>:对应两个await。

为了实现async,await编译器将每个被async关键字标记的方法编译为一个方法所在类的一个内嵌类,所有在方法体内出现的变量会被声明为这个类的field,如果是一个实例方法,那么this所代表的对象也被声明为一个field。这个类有两个核心成员:一个int来保存代码执行到那一步,暂且叫它step,一个方法来执行真正的动作,暂且叫做NextStep,整个逻辑看起来应该是这样的:

publicvoidNextStep()

[code]{
switch(step)

{

case1:

...

step++;

break;

case2:

...

step++;

break;

case3:

....

step++;

break;

.

.

.

}

}

[/code]

是不是觉得和yieldreturn很像?

而在async标记的方法大体是如此,假设方法被编译为一个命为AsyncMethodClass的类:

AsyncMethodClassa=newAsyncMethodClass();

[code]a.xxx=xxx;
a.yyy=yyy;

.

.

.

a.NextStep();

[/code]

那么编译根据什么来决定一个方法分成几个块呢?实际上,编译器根据一个async方法中出现的await关键字来进行分布,假如有一个await,那么应该有2个Step,假如有2个await,那么应该有3个Step。每一个Step,应该是以上一个Step中的await的EndWait开始(第一个Step除外),并以下一await的BeginWait结束(最后一个Step除外)。看到这里就理解了为什么async只能被标记在无返回值的函数上,因为NextStep函数必须要是一个无返回值的Action类型,传递给await的BeginWait方法,因此在上述代码中,在最后的第7行没有办法使用理论上的:

returna.NextStep();



弄明白了编译器干的活之后,最后来看一个实例,来自于AsyncCTPSamples的片段:

publicasyncvoidAsyncIntroSerial()

[code]{
varclient=newWebClient();


WriteLinePageTitle(awaitclient.DownloadStringTaskAsync(newUri("http://www.weather.gov")));

WriteLinePageTitle(awaitclient.DownloadStringTaskAsync(newUri("http://www.weather.gov/climate/")));

WriteLinePageTitle(awaitclient.DownloadStringTaskAsync(newUri("http://www.weather.gov/rss/")));

}

[/code]

经过上面的理论,不使用async和await关键字,改写如下:


TaskAwaiter<string>wait1;

[code]TaskAwaiter<string>wait2;
TaskAwaiter<string>wait3;

intstep=0;

WebClientwc=newWebClient();

privatevoidAsyncMethodWithoutKeywords()

{

switch(step)

{

case0:

Task<string>t1=wc.DownloadStringTaskAsync(newUri("http://www.weather.gov"));

step++;

wait1=t1.GetAwaiter<string>();

wait1.BeginAwait(newAction(AsyncMethodWithoutKeywords));

break;

case1:

WriteLinePageTitle(wait1.EndAwait());

Task<string>t2=wc.DownloadStringTaskAsync(newUri("http://www.weather.gov/climate/"));

step++;

wait2=t2.GetAwaiter<string>();

wait2.BeginAwait(newAction(AsyncMethodWithoutKeywords));

break;

case2:

WriteLinePageTitle(wait2.EndAwait());

Task<string>t3=wc.DownloadStringTaskAsync(newUri("http://www.weather.gov/rss/"));

step++;

wait3=t3.GetAwaiter<string>();

wait3.BeginAwait(newAction(AsyncMethodWithoutKeywords));

break;

case4:

WriteLinePageTitle(wait3.EndAwait());

break;

}

}

[/code]

可以发现,执行结果和使用async和await一样。

AsyncCTP的核心其实是Task的设计,CTP库为WebClient和WCF实现了Async到Task的工作,对于项目中出现自定义的一些方法则需要自己去定义Task,不过通过自己的方法定义Task,并且使用本文所描述的方法进行包装(尤其是在原先使用Emit或者使用dynamic关键字来做AOP的场合),那么可以抛开aysnc和await关键字,阻止编译器对代码的改动,这样的好处是断点调试可以正常进行。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: