【Asp.net精品源码栏目提醒】:网学会员为需要Asp.net精品源码的朋友们搜集整理了【精品】WF本质论01 - 其它资料相关资料,希望对各位网友有所帮助!
第 1 章 剖 析 WF 关于编程技术的书籍往往都是以介绍一个名为“Hello World”的小程序开始,这个程序用于在标准输出设备上打印一条简单的信息。
在此,我们也未能免俗,以下是这个程序的C版: “Hello World”之所以能够成为一个较为流行的编程起点,归功于它的简单,因为它避免了许多实际程序所需要关注的问题。
软件开发从业者肯定知道,扩展像“Hollo World”这样的程序,会很快碰到棘手的问题。
我们来看看这个名为“Open Sesame”的例子,它在打印传统的问候语之前,要求用户键入一个口令: 2 第1章 “Open Sesame”这个程序各个方面几乎都不怎么出彩,不过我们依然看到了它值得一看的地方:由于依赖于用户在控制台上所输入的一行内容,所以,从程序执行开始到结束,在执行时间上的花费可以是任意长的。
你可以编译并运行这个程序,并且把它搁置几周时间,然后再键入口令并最终打印出欢迎信息。
这一切都是设计使然。
“Open Sesame”是一个交互式程序(reactive program)的例子。
所谓交互式程序,就是程序在执行期间依赖外部实体的刺激,并对此做出响应。
有时,这个外部实体是一个人,有时,这个外部实体是另一个程序。
但无论是哪种方式,交互式程序都将花费大量的时间在等待这些外部刺激,所以,现在我们面临的挑战不是等同于编写诸如“hello,world”程序这般轻松了。
大多数的计算机程序是交互式的。
现实世界中,你会发现各种各样的处理过程几乎都有软件的身影:文档协作编辑、客户订单管理、原始资料提取、纳税申报单的预备、供应、产品开发管理、在线商店、客户关系管理、车间和仓库操作的协调等。
这个列表的内容还会不停地增加。
在这些处理过程中,交互式程序需要对人或者其他程序所提供的输入信息做出适当的反应。
一些交互式程序的开发使用了某些架构,比如
ASP.NET和Java Servlets。
而另一些原生(homegrown)解决方案则直接构建在执行环境之上,这些执行环境包括通用语言运行时(CLR)和Java虚拟机(JVM)。
还有一些程序则是用C或者(非托管)C之类的语言编写的。
然而,如果我们观察这些交互式程序的编写过程,就会发现大部分的程序跟前面提到的“Open Sesame”程序没什么共同点。
现在就来看一个Web Service程序(Web应用程序也不失为一个极具启迪性的选择),这个程序完成了和“Open Sesame”程序一样的功能。
现在,我们把“Open Sesame”程序移植到
ASP.NET Web service上来: 部 析 WF 3 不难看出,这个Web service有两个操作。
但是在这个程序里已经完全找不到“OpenSesame”中的控制流了,实际上,PrintKey必须在PrintGreeting之前被调用,并且,每一个步骤都必须严格地执行且仅可以执行一遍,只有这样,程序才是成功地完成了执行。
为了建立操作顺序的约束,我们添加以下黑体字标出的代码来修改Web service: 我们现在使用一组运行时检查以确保程序有正确的Web service控制流,但那些程序逻辑既分散又不明显,而且还很容易导致错误。
“Open Sesame”控制台程序中那些原本一目了然的顺序变成了难以理解的逻辑被分散到了Web service的各个操作中。
设想一下,仅仅提供WebService的
源码,也不用关心数据流,以求达到理顺程序控制流的目的,对于仅仅包括两步操作 4 第1章的简单例子,我们也许只要花数秒钟时间就能理顺其中的关系,但倘若是一个规模十倍于此的Web Service程序,并且该程序中还包括分支和循环的控制流,我们又将如何自处? 为什么我们不用更自然的C控制流结构来指定Web service操作之间的关系,以达到约束何时发生什么事件的目的?毕竟我们是在用C编程。
“Open Sesame”控制台程序有着精确的控制流,并操纵着程序所需要的局部变量。
为什么不能用这种方式编写Web Service、类似的Web程序,或者任何实际的程序呢? 我们有两点解释: 在“Open Sesame”控制台程序中,Console.ReadLine会阻塞调用线程的执行。
程序也许 会花费数天时间在等待输入上。
如果有几个这样的程序在同时运行,他们都在等待输入, 系统就会慢慢地停下来。
这种线程处理方式并不适用于现实世界的程序,尤其是那些部 署在多用户环境的程序。
现实世界的处理过程往往都要持续相当长的一段时间— 几天,几个星期甚至几个月。
我们当然希望在此期间操作系统进程(或者CLR应用程序域),能保持正常运行。
对于一个只具备教学意义的控制程序来说,上述这些问题很少会困扰我们。
所以,我们能够用“Hello World”这种非常自然的方式来编写“Open Sesame”程序。
阅读它的代码就能精确地知道程序做了些什么,但是对于我们的Web Service程序,这恰恰相反。
开发Web Service和Web应用程序时,伸缩性和健壮性往往更重要。
ASP.NET运行时设计成可以有效地管理多个服务和应用程序,并能有效地维护独立会话的状态(在正确的配置后,会话能够跨越主机)。
然而,代码是不会说谎的,程序的伸缩性和健壮性是需要极大代价的。
在上述Web Service中,两个操作之间共享的key变量利用了一个弱类型的名称值对(name-value pair)。
此外,为了确保操作的执行顺序而需要在每个操作的开始处检查变量(比如key)是否已经赋过值,这种逻辑十分别扭。
以此看来,更好地建立程序伸缩性、健壮性和更自然地表达交互式程序的状态和控制流,仿佛是鱼和熊掌不可兼得了。
世界上成千上万的Web程序和Web service证实了,为了完成工作,开发者心甘情愿地受限于今天的编程模型。
尽管如此,Web编程范例仅仅是交互式程序的一部分合适的解决方案。
当务之急便是找到一种更好的、多用途的方式来开发交互式的、基于网络的程序。
这种方法必须具备以下几个特点: 1. 存在一种编写交互式程序的方式,它并不牺牲自然的控制流结构,相反,事实上这种编写方式恰恰增强自然的控制流结构。
2. 存在一种既具可伸缩性又能健壮地运行交互式程序的方式。
1.1 线程进程灵活性 我们为“Open Sesame”所做的
ASP.NET Web Service解决方案具有可伸缩性和健壮性,但控制台程序却没有。
让我们来仔细看一下下面这条语句,它看起来似乎是问题的根源。
问题的重点在于:当“Open Sesame”控制台程序调用Console.ReadLine后,线程就会阻 部 析 WF 5塞(忽略错误的场景),直到有相应的信号到达后,线程才会继续执行。
有些场景要求相当多的“Open Sesame”实例(或者其他类似的程序)同时运行,让每个程序实例拥有高昂的线程资源(的做法)很难构建出伸缩性强的解决方案。
解决这个问题的一个常见的方法是采用异步方法调用的方式分出一个线程去执行。
举个例子,按照标准的.NET Framework模式的做法,ReadLine方法可以通过一对Begin End方法来达到异步调用的目的。
在BeginReadLine方法内部,建立了一个工作请求,并把它加入到CLR的工作队列中。
CLR线程池中的某个线程会异步地处理这个请求,但在此期间,BeginReadLine方法将会直接返回到调用者,而不是等待请求处理完成。
调用BeginReadLine的线程可以通过轮询System.IAsyncResult对象的IsCompleted属性来获取当前的工作状态,或者也可以使用AsyncWaitHandle属性来等待工作完成,这种方式比轮询高效。
以下为后者的示例代码: 尽管这个程序使用了异步调用,但它仍然占用着一个线程,用来调用IAsyncResult对象的WaitOne。
注意到BeginReadLine方法的两个参数,就可以使我们跳出上面的两难境地。
.NETFramework中的异步方法允许调用Begin时传递一个System.AsyncCallback类型的委托,Begin会在异步方法完成后调用这个委托。
Begin的另一个参数是一个对象,这个对象在回调和调用代 6 第1章码之间共享任意状态 ,本例中是NULL。
以下是使用异步回调机制的“Open Sesame”: 尽管这个程序还是阻塞在最初的线程上(这回是因为线程无限期的休眠),但是,对异步回调的使用使得一部分程序逻辑得以运行在任意线程上。
ContinueAt方法是在不同于Main的其他线程上执行的。
这个版本的“Open Sesame”通过非常简单的方法实现了线程灵活性。
我们通过异步回调将程序逻辑拆成几块,又链接在一起。
有一点很重要:在线程间共享的key变量的实现方式已经不同了。
在原来的程序中,key是一个基于栈的局部变量,现在,key是一个静态字段,它具有跨方法的可见性。
对于强类型的数据,这种栈无关的方法是降低程序对给定线程的依赖性的关键。
1.1.1 书签 现在看来,通过使用异步方法调用和AsyncCallback,我们已经把“Open Sesame”程序(的一部分)改造成具有线程灵活性了。
但进程灵活性也是可伸缩性和健壮性的要求之一,对于这一点,我们还无所作为。
这里的状态和之前的Web service中那种弱类型的会话状态变量是有区别的。
部 析 WF 7 上述所示的方法确实给了我们一些启示。
ContinueAt方法的委托运行起来活似一个书签(Bookmark)— 一个逻辑定位点。
当受到适当的外部刺激后,程序可以从这个位置恢复执 行 。
这给了我们一点提示: 1. 我们可以给书签命名,还可以为书签提供一个管理器。
2. 这个书签应该可以序列化,可以从持久存储介质中存取书签。
3. 我们可以编写一个监听器程序,需要分发到书签去的数据必须由这个程序来分发。
定义一个名为Bookmark的类,这个类实质上是自定义委托BookmarkLocation的具名封装(named wrapper)。
书签由BookmarkManager来管理: 一个书签即代表已经冻结的程序的一种延续(continuation) 。
书签的ContinueAt属性指 定了程序的物理恢复点。
书签是具有名称的,所以不仅能通过名称访问书签,还能独立地操纵 书签,而不依赖于物理恢复点(多个书签可能共享同一个物理恢复点)。
BookmarkManager简单 地维护着书签集合。
调用BookmarkManager.Resume方法后,程序会在书签中ContinueAt属性所 指定的定位点处恢复执行。
通过书签的Payload属性以获得使程序恢复的输入数据(外部信号)。
Bookmark类标记为Serializable,因此书签管理器可以将所有挂起的书签保存到持久存储 媒介中,比如数据库。
这样的话,只要能访问持久存储设备(比如,提供了数据库连接串), BookMarkManager就能可靠地获得书签集合,也就无论何时何地都能实例化。
一旦交互式程序 当你一觉醒来希望继续看昨晚那本小说时,可以从书签的位置继续阅读。
冻结程序的延续是指:阻塞的线程(即冻结的程序)一旦获得了可以继续执行的机会,可以通过书签(委托 的包装)来恢复执行。
书签保存了冻结程序再次执行的所有必要信息,所以是程序冻结后的延续。
8 第1章因为等待外部刺激而阻塞了,我们就能将它钝化(passivate)—将它的书签集序列化并保存到持久存储媒介上。
为了让整个程序能参与到书签机制中去,我们需要彻底消除(而不仅仅是减少)对栈的依赖。
这意味着不能再像之前的程序那样,在Main方法中调用Thread.Sleep方法,而需要编写Open Sesame类,这个类拥有一些实例方法和分配在堆上的状态。
以下是“Open Sesame”的新实现: 现在,“Open Sesame”程序可以用一个OpenSesame类型的对象表示了。
创建一个这样的对象,并在监听器 的上下文中“运行”(调用Start方法,而不是Main)。
这相当简单: 监听器可以获得来自任何地方的输入,比如数据库、MSMQ、Web service、Web应用程序,甚至是控制台。
这些逻辑跟“Open Sesame”程序无关,所以暂时没有引入。
部 析 WF 91.1.2 可恢复语句组件 上述代码都是用于单个程序单个实例的,我们需要一个运行时(runtime)来帮助我们管理各种类似程序的多个实例。
所谓运行时,就是与OpenSesame类相似程序的执行环境。
我们把这个执行环境命名为虚构运行时(mythical runtime): 虚构运行时可以运行ProgramStatement对象。
我们需要明确一点:OpenSesame类已经是一种全新的程序了。
假设有个合乎情理的需求要求OpenSesame作为单一语句能在较复杂的多语句程序中使用,那么我们称其为一种可恢复语句组件(Resumable Program Statements)。
ProgramStatement类型规范了(程序)执行的入口点,并且作为所有可恢复语句组件的基类。
在给出了ProgramStatement的定义后,OpenSesame类需要做如下修改: