具体实施方式
本发明现在将参照附图来描述,贯穿附图,相同的参考标号用于指同样的元素。在以下的描述中,为了便于解释,陈述了若干具体细节以提供本发明的彻底的理解。然而,很明显,本发明能够不用这些具体细节来实践。在其它实例中,以框图形式示出公知的结构和设备,以便于描述本发明。
如在本申请中所用的,术语“组件”,“系统”和“机制”指的是计算机相关的实体,或者是硬件、硬件和软件的组合、软件、或者执行中的软件。例如,组件能够是(但不局限于)在处理器上运行的进程、处理器、对象、可执行代码、执行的线程、和/或计算机。作为说明,运行在服务器上的应用程序和该服务器都能够是组件。一个或多个组件能够驻留在进程和/或执行的线程中,并且组件能够在一台计算机上定位和/或在两台或多台计算机之间分布。
以下是本发明的各个方面的描述。应该注意,虽然本发明是关于在Visual Basic(VB)环境中利用本系统和方法的各方面讨论的,但可以理解,在此揭示的概念能够结合任何面向对象(OO)环境中采用,而不会脱离本发明的精神、范围或功能。
一般而言,本发明的一个方面针对一种系统和方法,它能够采用VB.NET品牌环境中的语言扩展以支持通过消息传递、合约和配合的异步编程。简而言之,根据所揭示的本发明的各方面,VB配合允许描述协议并相应地编码服务或客户机。可理解,本发明减轻了对于明确编码和管理传统状态的要求。
最初参照图1,示出了根据本发明的一个方面的面向对象系统100的通用框图。如前所表明的,为易于理解,在此将针对VB应用来描述系统100。虽然VB环境将主要用于讨论本发明的各方面,还应理解,系统100能够应用到任何已知的面向对象的语言,而不会脱离在此描述的本发明的范围。通常,系统100能被配置成包括客户机102、合约组件104、配合组件106、以及多个目标服务1081-108N,其中N是整数。将可理解,目标服务1081-108N将在后文被统称为目标服务108。
合约组件104能够包括有效负载携带消息或者消息集,以及标识消息的实现时间表的协议。将会理解,合约组件能够可选地针对方法或方法集的传输。
配合组件106能够被配置成解释该合约并促进服务108的并行性和/或并发性。例如,配合组件106能够被配置成便于多个消息以及消息的多个目标的处理。虽然对本发明是新颖的,但可以理解,图1的配合组件增强了通过到/自服务的消息传递统一面向对象和面向消息的环境的低层新颖概念和改革。
图2示出根据本发明的一方面交换合约和消息的方法。尽管为了简化解释,在此例如按流程图的形式示出的一个或多个方法被示出和描述为一系列动作,然而可以理解和明白,本发明并不局限于动作的次序,因为根据本发明,某些动作可以在此示出和描述的不同次序发生和/或与其它动作同时发生。例如,本领域的技术人员将理解和明白,方法能可选地被表示为一系列相关的状态和事件,如状态图。此外,并非需要所有示出的动作来实现根据本发明的方法。
在202,合约声明合约-如在此所述的,将理解,该合约的声明能够包括定义多个消息或方法,以及一个可任选的部署模式。在204,合约中包含的消息(或方法)被发送到目标服务。下一步,在206,该消息(或方法)被接收并且引起转移。最后,消息在208被接受并且随后在210被反编译以便实现合约并采用被包括在其消息和/或方法中的代码。
并发模型
再次参照图1,应注意到,带客户机102和服务108之间的共享状态的并发执行已被证明即使对于熟练的编程人员也是有问题的。尽管各种强制向着客户机102和服务108之间的并发执行和异步交互来推拉软件,然而实现正确性中的困难仍是障碍。本发明针对一种系统和/或方法,它采用一种并发模型来减轻在编写正确的并发程序时的某些复杂性。
与传统实现相反,图1中示出的本发明的示例性VB配合中,没有二行共享存储器内状态的执行并发地执行。如所示,本发明采用服务108以便于并发执行。该服务并不与各个服务外部的任何代码共享状态。另外,为完成异步通信,图1中示出的本系统利用合约组件104或者“消息传递”技术,它在语言中表现为明确的Send(发送)和Receive(接收)操作,并且在配合组件106中实施。
将会理解,异步消息传递的引入需要用于同时管理多个服务之间的通信的机制。因此,用于协调并发服务之间的通信的机制的集合被称为“配合”,并且由图1的配合组件106所使用。
如以下将更详细地讨论的,提供了引入伪并行执行(如,Split(分裂))的语言机制。然而,这些机制实际上提供了并发等待而不是并发执行。服务108和配合组件106的组合提供了并发和异步行为,而没有竞争状态和未同步存储器访问的风险。根据本发明,两个服务之间的消息交换作为一种由合约组件104支配的对话而发生。合约组件104提供服务108的消息传递行为的规范,并且允许核查个别的服务108的某些形式的正确性(例如,与消息交换的模式的一致性、免除死锁),而不访问它所交互的服务的实现。这样,可以明白,在理解服务的行为中的主要的人为因素是其实现的合约。
合约
参照图3,本质上,合约104能够被定义为用于异步消息传递的接口声明,而具有更丰富的语义。换句话说,合约利用消息组件302来指定一组消息(或方法)以及一个可任选的协议,或利用描述可允许的消息交换序列的模式组件304。另外,合约组件104能够采用扩充已存在的合约组件的合约扩展组件306。
图4示出根据本发明的各个方面用于声明合约的方法202。如在此所述,可以理解,该合约的声明能够包括定义多个消息或方法以及一个可任选的格式。参照图4,并前进到402,声明一种合约类型。特别地,根据VB的例子,合约声明创建两个VB类型,表示在合约上的客户机和服务器观点。例如,声明“Contract C”创建类型“C”(表示客户机观点)和类型“Implements C”(表示服务器观点)。作为例子,以下是用于闹钟服务的一个示例性合约:
Contract AlarmClock
In Message Set(t As TimeSpan)
In Message Cancel()
Out Message Alarm()
Pattern
Start:
Set-->Alarm Set
AlarmSet:
Cancel-->End
Alarm-->AlarmSet
End Pattern
End Contract
另外,以下是用于闹钟服务的合约,它扩展合约以添加小睡能力:
Contract SnoozeAlarmClock
In Message Snooze()
Pattern
AlarmSet:
Snooze-->AlarmSet
End Pattern
End Contract
为了提供上下文和为了易于理解,合约的语法和语义的讨论在下文提供。作为例子,
合约语句组→
合约语句[扩展或详述语句]{主宿语句}{合约元素语句}[模式语句组]{合约语句组}结束合约语句
合约语句→
CONTRACT标识符
结束合约语句→
END CONTRACT
扩展或详述语句→
扩展语句|
详述语句
合约元素语句→
消息语句|
合约语句组|
类型定义
消息语句→
消息方向MESSAGE标识符[参数列表]
消息方向→
IN|
OUT
模式语句组→
模式语句{状态转移语句组}结束模式语句
模式语句→
PATTERN
结束模式语句→
END PATTERN
状态转移语句组→
状态语句{名字转移语句}[其它转移语句]
状态语句→
标识符[OVERRIDES名字]:
名字转移语句→
[名字转移表达式]-->状态目标{AND状态目标}
名字转移表达式→
名字转移操作数[名字转移操作符 名字转移表达式]
名字转移操作数→
名字|
(名字转移表达式)|
[ANY]消息方向MESSAGE
名字转移操作符→
THEN|
AND
OR
状态目标→
[IMMEDIATE]名字|
END|
STOP
如早先参照图3所讨论的,合约能够指定一组消息302,和描述可允许的消息交换序列的可任选模式304,和/或如图3所示的合约扩展306。应该注意,如果没有指定模式304,则指定消息的所有序列都是准许的。合约扩展机制306是用于添加到合约,使得在使用被详述的合约分类型的连接器与使用详述合约分类型的连接器之间的对话成为可能。
再一次参照图4并前进到404,可声明消息。下一步,在406,能够可任选地指定模式。在406中指定的模式能够被指定为状态机。该状态能够由状态转移语句组中的标签命名。在以上闹钟例子中,起始状态被命名为“Start”。消息发送和接收以及其它合约的执行引起状态机中的转移。可以构想,如果状态转移语句组包括一个以上状态转移语句,那么该模式允许该组中任何一个转移。
在408,可定义用于模式的触发器。特别地,状态转移语句中箭头左边的名字是触发器,并且能够命名消息或合约。下一步,在410,标识目标。该箭头右方的标识符是目标,并能命名状态。如果不存在触发器,那么如果发生目标状态的触发,就发生转移。如果触发器是合约,则效果是执行该合约模式的一个新实例。如果存在一个以上目标,那么转移将并行地到所有的目标。这种情况被称为分裂转移。如果目标是Immediate(直接),当第一个触发器开始时发生转移,而不是当最后的触发器完成时发生。应该注意,当触发器是合约时这是特别有用的。
根据一个例子,状态
S0:
M1 Then M2-->S2
等同于
S0:
M1-->S1
S1:
M2-->S2
同样,状态
S0:
M1 Or M2-->S2
等同于
S0:
M1-->S2
M2-->S2
另外,状态
S0:
M1 And M2-->S3
等同于
S0:
M1-->S1
M2-->S2
S1:
M2-->S3
S2:
M1-->S3
本领域的技术人员将理解,“Or”、“And”和“Then”具有与分别在表达式句法中的“Or”、“And”和“AndAlso”相同的优先关系。
在一个方面,作为“In Message(入消息)”或“Out Message(出消息)”给出的触发器能够表示在恰当方向上行进的合约中声明的任何消息。在另一方面,作为“Any In Message(任何入消息)”或“Any Out Message任何出消息)”给出的触发器表示在恰当方向上行进的任何消息,并且能够是没有在合约中声明的消息。
个给定的状态能够为一给定的触发器作出最多一个转移。根据本系统和方法,如果状态为消息作出转换,则该状态接受该消息。状态能够接受一个以上消息,并且由状态接受的消息并不必要都在同一方向上行进。例如,如果所有的消息不在同一方向上行进,则由状态S接受的任何消息的目标状态将忽略这些消息,该状态S状态不接受由在相反方向上行进的S接受的所有消息。
到End的转移终止了状态机的当前行。到Stop的转移终止了状态机的所有行。应该注意到,在没有分裂转移时,End和Stop是等效的。
参照图4,根据本系统和方法,消息能够通过连接来发送和接收。连接是带类型的数据,该类型是在402声明的合约类型。将理解,合约类型能够通过命名合约来指定。另一方面,连接能够是带类型的数据,该类型是合约实现类型。合约实现类型能够通过在合约名字之前指定“Implements”来指定。带有合约类型的连接能够发送In消息和接收Out消息。一个带有合约实现类型的连接能够发送Out消息和接收In消息。
分裂转移
如果在410状态转移具有一个以上目标,那么该转移被称为并行地到所有目标的分裂转移。例如,如果从该分裂转移的某些路径在S处汇聚,则分裂转移在状态S部分地合并,并且如果所有的路径如此,则完全合并。如果状态T是分裂转移的直接或间接目标,则不可能存在不通过分裂转移到T的路径,除非分裂转移在T处或T处之前已经完全合并。如果分裂转移在状态S处合并,则在来自分裂转移的所有路径都到达S处之前没有来自S的转移是可能的。非正式地,分裂/合并正确地嵌套,并且不可能有到分裂/合并的中间的转移。
作为例子:
模式
Pattern
Start:
M1-->Start And S1
M2-->S2
S1:
M3-->S3
S2:
M4-->S3
S3:
M5-->End
End Pattern
接受消息序列M1、M1、M2、M4、M3、M3、M5。包括S1的每一分裂转移要求M3转移到合并状态S3,从而能够有相等数量的M3和M1消息。
模式
Pattern
Start:
Conl-->Immediate S1 And S2
S1:
M1-->End
S2:
M2-->End
End Pattern
允许M1与Conl消息混合,但要求M2发生在Conl完成之后。应注意,这个模式还示出End能够是合并状态。
合约扩展
根据本系统和方法,在412,可采用合约扩展机制(如图3的306)来添加到合约中,从而在使用被详述的合约分类型的连接器和使用详述合约分类型的连接器之间的对话成为可能。只有对话的一个端点具有关于使用哪个合约的选择。该判定由扩展的类型作出。
语法和语义的例子如下:
扩展语句→
EXTENDS名字
这里,在Extends语句中的名字能够命名合约,它不是包含合约或者扩展或详述包含合约的合约。
如果合约B扩展合约A,那么:
·A的所有消息是B的消息。
·A的所有状态是B的状态。
·由A主存的合约也由B主存。
·B不能移除来自从A继承的状态的任何转移。
·B能够添加消息、状态和主存的合约。
·B能够添加到从A继承的状态的转移。所有添加的转移能够是用于在同一方向上行进的消息。例如,如果添加的转移是用于In消息,则客户机能够使用A或者B连接到实现B的服务。如果添加的转移用于Out消息,则客户机能够使用A或B连接到实现A的服务。
在程序文本中,仅当B添加到它的转移时在B中重复A的状态,并且仅出现所添加的转移。应该注意,在一个设计中,继承的转移是可视的,但可视性被标记为不变的。
连接器
再次参照图2,根据本系统和方法,动作204和206之间的消息交换通过连接发生。连接的每一端点的代码通过连接器发送和接收消息,该连接器由连接器工厂创建。对于合约类型CT,客户机方连接器具有类型CT,而服务器方连接器具有类型Implements CT。
配合
此外,Split操作生成多行执行,其中任何一个执行在它退让之前执行,在这一点上其它执行的任一个能够运行。各种语言操作能够使得一行执行退让。在这些之中是Receive(如,等待消息到达)和Wait(如,等待一个特定的时间间隔)。
如果其中一个的退让能够允许在另一个中的一行运行,则一组方法构成一个配合单元,或者能够说配合在一起。注意,这并不允许执行前进到越过方法调用,知道调用返回。配合类在一个实例方法(包括基类和导出类中的方法)为给定的实例配合在一起。
配合类用“orchestration”修饰词来声明。配合类不能具有共享的方法或属性。根据这个方面,配合操作是VB语句,并且,服从于某些约束,能够出现在任何方法中。
再次参照图2,在204,Send(发送)语句能够被如下配置:
发送语句→
SEND[TO表达式]发送消息描述符
发送消息描述符→
发送特定消息|
发送消息
发送特定消息→
标识符[(自变量)]
发送消息→
MESSAGE表达式
根据这个方面,Send语句通过一特定的连接器发送消息,该连接器是可任选的表达式。缺少连接器表达式可暗示消息将通过主连接器发送,并且只在服务中准许。将会理解,连接器表达式能够是合约或者合约实现类型。
如果存在,标识符能够命名合约的消息,并且该消息能够具有适当的方向。类似于方法调用,自变量是对照消息参数进行类型核查。
如果Message关键字存在,则该表达式能够被转换成System.MessageBus.Message,并且标识要发送的消息对象。
类似地,在206,Receive(接收)语句能被如下配置;
接收语句→
接收子句
接收子句→
RECEIVE[FROM表达式]接收消息描述符[WHEN表达式]
接收消息描述符→
接收特定消息[接收消息]|
接收消息
接收特定消息→
标识符[({接收参数列表})]
接收消息→
MESSAGE接收参数
接收参数列表→
接收参数{,接收参数列表}
接收参数→
标识符[AS通用类型]
Receive语句能够通过一特定的连接器接收消息,在该消息到达阻断。缺少连接器表达式暗示该消息将通过主连接器接收,并且只在一个服务之中准许。连接器表达式能够是合约或合约实现类型,标识符(如果存在)能够命名该合约的消息,并且消息能够标识适当的方向。在Receive语句中声明的参数范围类似于局部变量声明。另外,没有在接收语句中声明的参数能够在别处被声明为局部变量。
如果Message关键字存在,则相关联的参数能够被声明为System.MessageBus.Message,并且Receive能够将接收到的消息对象分配给局部变量。如果是Message关键字存在并且没有给出特定的消息名字,则Receive能接收满足过滤器表达式(如果存在)的任何消息。
同样,如果存在,Receive中的When表达式担当布尔过滤器。如果过滤器表达式存在,则如果过滤器求值为True(真),该消息被接收,否则它被忽略,直到某个其它的Receive消耗它。过滤器表达式能够访问它所附加的接收子句的参数接收到的消息对象的成员,但没有其它的非常量声明。缺少过滤器表达式等同于始终具有值“True”的过滤器表达式。
例如,以下形式的Receive语句
Receive From C Mumble(P As T)
NextStatement()
等同于
Dim P As T
Select Receive
Case Receive From C Mumble(P)
End Select
NextStatement()
Wait(等待)语句能够如下配置:
等待语句→
WAIT表达式
Wait语句将执行阻断由该表达式给出的指定间隔,它能够是System.TimeSpan类型。Wait语句允许一行执行退让,即使该间隔是零。
以下形式的等待语句
Wait Span
NextStatement()
等同于
Select Receive Within Span
Case TimeOut
End Select
NextStatement()
注意:重要的是Threading.Thread.Sleep()不在配合代码内使用,因为它能够阻断线程而不允许配合中的另一行运行。
一旦接收到,转移能在此被引发。Begin(开始)语句能够如下配置:
开始语句→
开始子句
开始子句→
BEGIN标识符 类型和初始化程序
标识符能够被声明为局部变量。变量的类型能够是合约类型或合约实现类型。初始化程序能够存在。
Begin语句等待由指定的合约支配的对话的开始。本领域的技术人员将理解,Begin语句表示一类内嵌服务。当代码的另一主体创建合约类型的反转的连接器,并且然而通过该连接器发送消息时,这样的对话开始。当对话已开始后,对初始化程序求值,它的值被分配给变量,并且执行继续进行。
Begin提供一种使子对话相关的机制。对于相关的子对话,该连接器的初始化是New表达式,它带有连接器作为其唯一的自变量。连接的另一端点的连接器由带有连接器作为其唯一自变量的New表达式创建。同样,被提供作为自变量的两个连接器将被连接。
以下形式的Begin语句
Begin C1 As New CT(C0)
NextStatement()
等同于
Dim C1 As CT
Select Receive
Case Begin C1=New CT(C0)
End Select
NextStatement()
最后,消息能够在208被接受。因此,Accept State(接受状态)语句能够被如下配置:
接受状态语句→
接受状态子句
接受状态子句→
ACCEPT[FROM表达式]标识符
Accept State语句等待直到连接转移到特定的合约状态。缺少连接器表达式暗示该状态将通过该主连接器来接受,并且只一个服务中准许。该连接器表达式能够是合约或者合约实现类型,并且标识符能够命名合约的状态模式的状态。
接受状态语句并不消耗任何消息。特别地,造成状态转移的消息未被消耗。取消阻断要求状态转移。如果当接受状态语句开始时连接已处于被命名的状态之中,则接受状态语句等待直到连接重新进入该状态。
可以理解,接受状态语句作为选择接收语句的一部分以及捕捉接受语句的一部分特别有用。
以下形式的接受状态语句
Accept From C S
NextStatement()
等同于
Select Receive
Case Accept From C S
End Select
NextStatement()
Select Receive(选择接收)语句能如下配置:
选择接收语句组→
选择接收语句{情况接收语句{可执行语句}}[情况超时语句{可执行语句}]结束选择语句
选择接收语句→
SELECT RECEIVE[WITHIN表达式]
情况接收语句→
CASE情况接收子句{,情况接收子句}
情况接收子句→
接收子句|
开始子句|
接受状态子句
情况超时语句→
CASE TIMEOUT
将会理解,SelectReceive语句能够接收一组消息的一个(或多个)(其每一个能够通过不同的连接器)、和/或启动一个或多个对话、和/或接受一个或多个连接的状态、或超时、并且执行关联于消息/对话/状态或超时的代码主体。超时表达式必须指明时间跨度。
如果在情况接收语句中存在一个以上情况接收子句,则所有子句能够被满足,以触发关联于该情况的代码主体的执行。
Split(分裂)语句能够被如下配置:
分裂语句组→
分裂语句{分裂行语句{可执行语句}}结束分裂语句
分裂语句→
SPLIT
结束分裂语句→
END SPLIT
分裂行语句→
LINE
Split语句能够适当地将执行划分成执行的逻辑行。这些行能够按词汇次序顺序地执行,除非当一行阻断等待消息时下一个未阻断的行运行。可以理解,Split语句的没有两行能并发执行。Split的一行中的Receive语句不能够接收对从Split的另一行发送的消息的答复。本领域的技术人员将理解,这允许对特定消息的相关答复。
Split For Each(对每一个分裂)语句能够被如下配置:
对每一个分裂语句组→
SPLIT对每一个子句{可执行语句}下一语句
对每一个分裂语句组的主体能够如同每个迭代是分裂语句组的的一个单独行那样执行。通过集合的完整迭代能够在执行主体之前发生。
Split Select Receive(分裂选择接收)语句能够被如下配置:
分裂选择接收语句组→
SPLIT选择接收语句{情况接收语句{可执行语句}}[情况超时语句{可执行语句}]结束选择语句
Split Select Receive语句自动创建任何数量的执行行以处理接收的消息、启动的对话以及超时。
以下形式的Split Select Receive语句
Split Select Receive
Case Receive M From C
Receive M1 From C
End Select
类似于,但略微且明显不同于
Do
Select Receive
Case Receive M From C
Receive M1 From C
End Select
Loop
Split Select Receive语句只要行退让就能够被激活,而不是等待一行完成。任何行的控制到分裂选择接收外部的一点的传输能够终止分裂选择接收的所有其它行。
Catch Receive(捕捉接收)语句能够被如下配置。试验(try)语言能够具有接收消息的处理程序。可使用一种新形式的捕捉语句实现这个概念。
捕捉接收语句→
CATCH接收子句
Catch Accept State(捕捉接受状态)语句能够被如下配置。试验(try)语句能具有接受连接状态的处理程序。可使用一种新形式的捕捉语句来实现这个概念。
捕捉接受状态语句→
CATCH接受状态语句
New Exit(新退出)语句是被构想的。例如,新退出的种类能够是Line和Split。这些在Split、Split For Each和Split Select Receive语句中适用,并且在其它地方不允许。Exit Line(退出行)语句能够终止Split中的一行执行。Exit Split(退出分裂)语句能够退出Split并且终止Split的所有其它行。
退出任何形式的Split的控制任何其它传输能够具有Exit Split语义,而不是Exit Line语义。
服务
再一次参照图2,下一步,在210实现合约。根据本发明,服务(如,图1的服务108)提供合约的实现。在接收服务合约的启动消息之后,自动例示服务。服务通过其来实现它的合约的连接器被自动地初始化、隐含地可用于发送和接收消息、并能够明显地被称为“主(Primary)”。根据所描述的方面,服务能够具有名为“Run”的实例方法,它没有参数,也不返回结果,从而激活服务调用Run。
服务是配合类,它直接地或间接地从RuntimeService导出。如果没有指定基类,则它隐含地是RuntimeService。
再一次参照早些提出的闹钟例子,实现以上给出的闹钟的服务能够被如下配置:
Service DecentAlarmClock
Implements AlarmClock
Sub Run()
Receive Set(T As TimeSpan)
Select Receive Within T
Case Receive Cancel()
Case TimeOut
Send Alarm()
Receive Cancel()
End Select
End Sub
End Service
另外,以下是一个示例性服务,它被配置成实现早先例子的小睡闹钟合约:
Service NicerAlarmClock
Implements SnoozeAlarmClock
Sub Run()
Receive Set(T As TimeSpan)
Do
Try
Wait T
Send Alarm()
Receive Snooze()
T=SnoozeInterval
Catch Receive Cancel()
Return
End Try
Loop
End Sub
End Service
现在转向服务的语法和语义:
服务语句组→
服务语句{服务成员语句}结束服务语句
服务语句→
SERVICE标识符
结束服务语句→
END SERVICE
服务成员语句→
服务语句|
消息语句|
类成员语句
根据以上所述,服务不可能具有实现语句和任何消息语句二者。然而,如果存在实现语句,就会存在一个,它能够命名单个类型,并且这个类型能够是合约。
服务能够具有隐含的主连接器,它实现合约。该实现的合约能够在实现语句中被命名。如果服务被声明而没有实现语句,那么该服务能够声明能够通过其主连接器的消息,并且将从服务的方法中导出模式。换言之,任何服务都实现合约。
主连接器能够在服务内作为Primary而可用。如果连接器表达式在Send和Receive语句中被省略,则该连接器能够隐含地是Primary。
对服务的调用返回分类型为服务的实现合约的连接器的新实例。
现在转向本发明的更一般性的讨论,当谈到在n层或任何一般的分布式环境中的客户机和服务器时,重要的是非常严格地定义术语以避免混淆。例如,在VB环境中,“服务客户机”是不由正发送的消息启动的那些配合对象。换言之,服务客户机能够用某些其它方式来启动。另一方面,“服务”是由正发送给它的消息创建的任何对象。可以理解,不存在其它方式来启动“服务”。作为稍许宽松的概念,“客户机”是任何代码片段,不管是服务还是服务客户机,它都启动与服务的通信。
合约
再一次,一般地参照合约,合约能够用在OO语言中来指定协议和它的消息。这个讨论将集中在三个部分:合约导入语句、消息声明语句和单一模式定义语句,如下所示:
Contract CtRequestResponse
In Message Request(Question As String)
Out Message Response(Answer As String)
Pattern
Start:
Request Then Response→End
End Pattern
End Contract
以上是称为“Request(请求)”的入站消息的定义,它将沿途传递一个String(串)。同样,定义称为“Response(响应)”的出站消息,它也将沿途传递一个String。注意,消息本身是名字并且不是数据的“丛(clump)”。相反,消息本身是其标识符,类似于在那一点上的类名。数据能够被定义为消息的参数。
另外,还定义了协议只允许任何“会话”中的两个消息:Request和Response。交互中这两个消息的方向由各自的消息声明暗示。可以理解,一个人的入站消息是另一个人的出站消息。根据本发明,这种观点是来自实现合约的服务的观点。
通过协议的每个交互包含来回交互、发送消息的两方,从而影响彼此的内部状态或具有某种有用的外部效果。在每个这样的两方耦合中,为清楚起见,各方被标记为实现方和使用方。
在所揭示的方面的VB配合中,这被称作是合约观点。将被理解,编译器将不允许入站消息由实现方发送或者由使用方接收,也不允许出站消息由实现方接收或者由使用方发送。
可观察到,以上的合约本质上是任何同步消息传递机制都允许的复杂性:一个消息走一个方向,而另一个则返回作为响应。如果没有返回数据,或者在过程调用上的任何参数,那么对应的消息被相应地调整,但是在同步模式中始终有一个单一的入站消息之后跟随着一个单一的出站消息。如将在以下讨论的,协议能够用各种各样方式来定义,并且VB配合可便于对这个强大的技术的完全计划性访问。
本质上,采用合约允许声明消息和模式。然而,合约能够把大量的安全和健壮性级别添加到任何协议实现中,因为它们能够允许编译器核查协议正由代码依附。至少,合约在运行时,并且经常在编译的时间被实施。
根据本发明,合约是在所揭示的方面的VB配合中的基础语言添加之一。以下是关于合约声明的细节的继续讨论。
消息声明
再一次参照图4,以上的例子示出一种声明合约的方法。特别地,在404,示出声明消息的动作。本领域的技术人员将理解,它们本质上共享与Sub声明类似的通用规则,并具有以下约束:
1.是“ByVal”、“Optional”、“ByRef”或“ParamArray”的所有参数声明都不被允许;
2.对于将被远程使用的任何协议,所有参数是可以串行化的;
3.所有消息必须有一个指定的方向;以及
4.消息不能够被重载。
模式声明
现在转到在406处的模式声明,任何模式声明的大多数基本构建块是消息引用。在上述例子中,可以理解,当在模式中引用消息时,仅仅需要提及它的名字。这是因为它的方向是公知的。另外,重载是不允许的,并且一旦被声明,除了方向和参数类型之外,附加的限制不能被放入到消息上。应该注意,例如,没有方法来指定上述例子中的串自变量必须是非空,即使这是所期望的限制。
模式通过定义协议的一组命名的“状态”来声明,例如,对话中对它具有某种意义的不同位置。任何模式的第一个状态被称为“Begin(开始)”。
一旦状态被定义,协议能够通过添加状态“转移”以指出协议状态如何改变来完成。可以理解,触发器和目标能够结合动作408和410来标识。在一个方面,这样的转移由消息触发。在另一个方面,消息能够由方法或导入的合约来表示。
作为例子,
Contract CtRequestResponse
In Message Request(Question As String)
Out Message Response(Answer As String)
Pattern
Start:
Request Then Response→End
End Pattern
End Contract
如上所述,短语“Request Then Response(请求然后响应)”是一个转移触发器,并且“End(结束)”是转移的结束状态。换言之,转移触发器能够是“复合”的。例如,复合转移触发器可通过使用“Then”对一组消息定序或者使用“And”无序地组合它们来使用。同样,还可能在消息名字之间使用“Or”来指定一组替换的触发器。一旦所有的触发器的消息都被发送或接收,转移也马上发生。
转移结束状态能够是一个以上状态。例如,转移能够“分裂”成两个或多个目标状态,它意味着该协议“位置”一次被一个以上命名状态所定义。在一个方面,目标结束状态还能够附加修饰词“Immediate”,它意味着到该状态的转移在匹配一特定的触发器之后立即发生,而不是等到完整的触发器完成。这在触发器包含合约名字时特别有用。
如以上讨论,可以理解,不具有任何模式的合约允许任何消息交换模式;具有空模式的合约不允许消息被交换。
合约扩展
另外,本发明的各方面采用如图4中在412所示的合约扩展。图5示出一种便于扩展合约的方法。通过根据该方法扩展合约,能够创建与老版本非对称地兼容的新版本。特别地,当扩展合约时,在502,添加新消息。同样,在504,新状态能够被添加到已存在的模式中。另外,新触发器能够在506被添加到已存在的状态中,服从于如下某些约束:
1.不能引入多义性。
2.不能改变已存在的触发器。
3.添加到已存在状态的触发器不能具有“In”和“Out”消息两者作为第一个可能的消息:该消息的方向要是同样的。
4.如果一个触发器被添加到一个已存在的状态中,并且如果该触发器是“In”消息,那么所有被添加到任何已存在状态中的触发器只包含“In”消息。
5.相反,如果一个触发器被添加到一个已存在的状态中,并且如果该触发器是“Out”消息,那么所有被添加到任何已存在状态中的触发器只包含“Out”消息。
根据这些约束,用于合约再使用的模型能够类似于类的继承。在一个方面,(如,仅仅“In”消息被添加到已存在的状态),新合约是客户机兼容的,并且在其它情况下,它是服务兼容的。
客户兼容的合约意味着该服务能够从旧合约升级到新合约而不需要让客户机知道。换言之,使用旧合约的客户机决不会发送触发转移到新状态的消息。另一方面,知晓较新合约的客户机可以仅当服务支持它时使用它。这样,可以理解,这种关系是非对称兼容的。
另一方面,如果合约是服务兼容的,那么客户机能够被升级以使用新合约并且继续具有与支持老合约或新合约的服务的对话。
以下是使用早先提出的请求/响应例子的修改的版本的示例:
Contract CtRequestResponse
In Message Request(Question As String)
Out Message Response(Answer As String)
Pattern
Start:
Request→S1
S1:
Response→End
End Pattern
End Contract
客户机兼容的例子:
Contract CtRequestResponse1
Extends CtRequestResponse
In Message RequestInt(Question As String)
Out Message ResponseInt(Answer As Integer)
Pattern
Start:
RequestInt→S2
S2:
ResponseInt→End
End Pattern
End Contract
如以上示例性代码所示,如果客户机仅仅知道旧合约,那么它仍然能够连接到支持新合约的服务,因为只要新消息永不发送,合约就可以互相交换。如上所示,服务没有能力辨别使用旧合约的客户机和简单地没有利用新特征的客户机之间的差别。
服务兼容例子:
Contract CtRequestResponse2
Extends CtRequestResponse
Out Message ResponseInt(Answer As Integer)
Pattern
S1:
ResponseInt→End
End Pattern
End Contract
在此,服务能够确定是否要超出已存在的合约。因此,已升级的客户机将没有能力断定服务是使用旧合约还是新合约。注意,在扩展模式中,仅仅新信息被列出:新状态和已存在的状态的新触发器。这避免了出错源,并且如果需要清晰性,旧合约模式能作为注释被包括在内。
将可理解,合约A可通过添加主存的合约并且不添加消息来扩展合约B。在这种情况下,A与B对称兼容。主存合约在本文的稍后将被更详细地讨论。
连接器
合约能够用于声明引用它们的某些内容。根据示例性方面,VB基于配合的代码的用于与其它代码通信的对象被称为“连接器(Connector)”。
至于关注到类型,连接器是合约引用类型。存在关于连接器的特性。例如,连接器具有定义良好的合约观点,它是其类型的一部分。它或者标识合约的实现方,或者是使用方。实现连接器不能被安排为使用连接器,反之亦然。根据这些规则,对于如何创建连接器以及如何定义它是实现的还是使用感到疑惑是合理的。
连接器能够使用连接器工厂来创建,后者能够由运行库定义(如,VB运行库)。大多数连接器工厂从服务URL或其它的连接器创建:
Dim cFact As ConnectorFactory(Of CtRequestResponse)=ServiceURL
此处的URI被称为服务引用。它能够是保留在配置文件中的某些公知的URI,或者它能在运行时由某些其它代理服务放在一起。例如,可能存在一个批发商出价服务,它确定最佳加以的位置所在,并将到该批发商的URI发送回请求者。
一旦具有了连接器工厂,能够很容易地使用对话操作符来创建连接器,该对话操作符被构建到连接器工厂类中:
Dim Connector_1 As CtRequestResponse=cFact
这个示例性语句创建了一个使用观点连接器,它引用由连接器工厂定址的服务,并且能够在它的主连接器上实现“CtRequestResponse”。连接器工厂的这一用途能够生成使用方连接器。
可以理解,连接器工厂还能够如下被直接绑定到实现观点连接器上:
Dim cFact As ConnectorFactory(Of CtRequestResponse)=Connector_4
这一方面在双方都在同一应用程序域内并且不需要分裂是公知的环境中是很有用的。因为它要求把代码添加到防止它被分发的应用程序中,它一般是被阻止的,但对于特殊情况中的表现很有用。先前的例子是使用观点连接器。
另一方面,实现连接器能够按两种不同的方式被声明。特别地,合约名字能够通过关键字“Implements”来修改,并且初始化程序表达式必须是一个不带自变量的合约调用,如下:
Dim Connector_4 As Implements Ordering=Ordering()
当使用观点连接器被初始化时,它被绑定到对应的实现连接器,但是实现连接器并不启动绑定,所以它不需要服务引用作为参数。这样,所得的连接器在创建之后解除绑定,并且不能用于发送消息,直到某些使用方连接器将其自身绑定到新连接器。
注意,如前定义的服务具有隐含的实现连接器,被称为其“主”连接器。它由运行时算法在该服务启动之前创建,并作为“Primary”字段对时间表可用。在发送和接收消息时,当没有其它连接器被引用时该连接器能被隐含,从而将很少看到实际上明确地引用主连接器的任何代码。如以下详述的,主连接器能用来创建用于多路复用的服务引用。
只要引用类型的变量能够被声明,就能够声明连接器。例如,在VM模块或其类似物中,连接器能够被声明为局部的、为参数、和/或为字段。它们能够通过引用被传递,并复制到其它变量,这类似于任何对象引用。根据本发明的一个方面,连接器的观点永远不能被改变。同样,编译器能够通过其强类型而如此确保。因此,在这方面,在定义或者使用连接器或通用服务的任何源文件中使用“选项严格关闭(Option Strict Off)”是不被允许的。
会话
关于本发明的配合的一个新颖方面是它能够减少关于线程、进程或其它方面的关注,它们是可伸缩多线程应用程序的典型问题。给予正确的关注和展望,线程化和进程隔绝能够成为一种应用程序分发和管理问题而非应用程序开发问题。
然而,通常并非是关于非分布式应用程序的问题,但对VB配合是必需的一种概念是会话的概念。
以下例子为容易理解而被提供。使用因特网购物或访问财务机构站点以访问帐户信息或付帐的用户应当熟悉在分布式应用程序意义上的会话。特别地,当访问一个e商务站点并且物品被添加到购物车中时,或者访问“My Account(我的账户)”页面时,该网站软件必须在存储器中分配某些对象,以使它能够记住访问者以及自从登录时间以来完成了什么。这些数据可以是临时的,且与通常存储在数据库中的长时间保持不变的订购信息或账户信息是不同的。
这种临时数据的生命周期被称为会话。在非分布式应用程序中,数据能够被保持在存储器中直到它成为无用信息。另一方面,该情况在分布式应用程序中则完全不同。对于分布式应用程序存在两个主要问题。首先,跨应用程序节点的对象引用并不存在。其次,当会话终止时,不能指望被建议。
第一点意味着如果依靠无用信息收集来清除对象,那么所有的临时数据都被非常快地看作为无用信息并被移除。应用程序结点然后将忘却关于曾经在其上的访问者的每件事情。从而,这种软件通常能够在某种类别的全局收集中分配会话专用数据结构,并且当对于同一会话一个新请求来到时检索它。
现在转到第二点,如果不知道会话何时会结束,该会话数据将永远不会变成无用信息,因为全局收集因它们所造成的存储器泄漏问题而注明。
为解决这些问题,会话通常被定时。当会话被首次创建时,能够设定定时器,并且在每次输入消息或请求时被复位。如果能够确定会话已结束,例如该web用户注销,则数据在这一点上被移去。如果不能够确定,那么当定时器到点时,会话数据也从全局散列表中被移除,并且数据成为无用信息。
可以理解,合理的超时间隔的确定完全是应用程序相关的。网站通常设定超时间隔在10和30分钟之间,但这不是普遍有效的设定。对于某些应用程序,若干秒可能更恰当,而对其它,则几小时更恰当。
根据示例性性VB配合,所有这些是自动支持的。在一个方面,有10分钟的默认超时值。如果10分钟不恰当,可以在App.config文件中重新配置超时。这种重新配置将在以下的关于“App.config设置”中更详细地讨论。
当时间表被首次启动时,不管它是服务还是服务客户机,其会话能够在任何事情发生之前被创建。该会话有效地成为服务类的实例。然而,它还被分配一全局唯一标识符,它允许客户机引用它。因此,该会话还被称为服务实例。可以理解,后者在需要指示正在引用存储器中的特定对象时特别地有用,而前者在想要从分布式应用程序观点观察它时特别有用。
服务实例通常具有与其相关联的多个连接器。最初,连接器只能够被约束于创建它们的时间表上,但是为了发送消息到另一时间表,连接器能够被绑定到始终是同一合约但是相反观点的其它连接器。一旦被绑定,连接器不能被重新绑定。
参照以上的例子,创建的前三个连接器在服务或合约调用返回时全部被绑定。在第四种情况下则不是。这是因为连接器只能用于创建某些其它连接器用于绑定到它的服务引用。一旦发生这一情况,通信可开始。
在以上web服务例子中,重要的是,处理会话的软件并不存储对会话本身外部的会话专用对象的引用。这样做意味着当会话被移除时,这些对象并不成为无用信息,并且违反了会话控制分布式应用程序中对象的生命周期的主要目的之一。
同样重要的是,这在对VB配合进行程序设计时完成,即使整个应用程序最初在同一应用程序域内。允许在会话期间创建的引用被存储在时间表外部将不允许自动管理最终将确定是否使该应用程序缩放的全局系统资源。
规则是简单的-仅仅在服务或时间表的主体中声明的引用中存储对数据状态的引用。换言之,不把它们存储到任何种类的全局变量中或收集中。这个规则是很容易遵从的,并且非常合理。同样,这个规则本质上非常类似于用于其它软件的基本重入规则。例如,避免了全局状态,相反,始终沿途传递数据作为参数。
仅当数据作为应用程序级数据属于时,即,数据在应用程序域中在会话内被共享,它应该被声明并存储在会话之外。即使这样,它应该担当表示可能存在需要重新考虑的关于应用程序设计的某些事情的危险信号。
时间表(配合)
现在参照图6,示出了配合组件的通用框图。如所示,配合组件能被示出为包括实现或时间表组件602和编译器组件604。根据本发明,时间表组件602是一个运行时概念,它没有源代码表示,但仍然对于以下章节的理解是关键的。简言之,时间表组件602是一个运行时对象,它用于允许面向消息的代码并行地等待一个以上消息。
抽象而言,对于示例性VB环境,所有的方法都具有一个时间表,借此当时间表完成时该方法返回。具体地,仅仅包含如以下所述的消息接收或等待语句的方法才需要时间表。VB编译器将确认大多数有效实现如同在无时间表实现中使用。
给出这个运行时定义,时间表能够被概括地描述为消息相关代码的主体,它在若干个方法之间分裂或者被包含在单个方法中。后一种情况是默认的,例如,带消息传递代码的每一方法定义它自己的时间表。
另一方面,前者是使用配合类来实现的,该类是从类System.Orchestration.RuntimeOrchestration直接或间接导出的所有的类。配合类对于一个实例上的所有方法共享单个时间表。该时间表能够由对任一实例的外部(如,不使用“Me”)调用所启动,并且跨同一实例上的所有内部方法调用扩展,直到原始的方法返回。
配合类在各个方面类似于常规的类,除了它们具有需要的基类,并且不能声明共享的方法或属性。
服务
此外,根据本发明,服务是指能够直接或间接地(如,从其它服务)从类System.Orchestration.RuntimeService中导出的配合类。同样,服务是运行时自动例示的。
为了提供上下文,以下讨论阐明关于服务的两个概念:1)由服务的主连接器实现的合约;以及2)它的启动方法。
首先,合约能够按两种方式定义:1)通过在实现语句中按名字来提及它;或者2)通过从时间表主体中导出。前者的例子如下:
Service StockQuoteService
Implements CtRequestResponse
End Service
此处的合约是前面讨论过的请求/响应。例如,服务接收证券报价符号作为串,并且以最后一次交易的价格的串表示来答复。当然,对响应使用不同的数据类型是较好的想法,但此处的关键点不是如何设计一个好的合约,而是如何在服务中使用合约。
服务始终是由调用该服务的客户机通过向服务发送消息来启动的,该消息将使用连接器绑定到在恰好服务被启动之前被创建的实现连接器。
保持服务定义的隔离是很重要的,它指定合约和时间表,并且它能够使得在若干服务器上的若干进程中,以及表示服务的特定会话的服务实例上可用。
应该理解,服务是类型定义,而服务实例是对象。然而,因为实际上永远不会具有(或能使用)对服务实例的引用,有时很难看到这种关系。服务仅仅通过连接器而交互。仅当消息传递是与服务交互的单一方式时,它们才能够以对系统支持可缩放应用程序设计必需的方式被重定位或复制。这样,服务不能定义构造函数或者使用“New”表达式来分配。
指定服务实现的合约的第二种方法是采用编译器以从包括在时间表中的代码导出它。这里,编译器分析该代码以解释它。换言之,不必编写合约定义。为使用这种机制,仅必须声明在主连接器上使用的消息。为了能够使用这种服务的合约来创建连接器,为服务导出的合约能够具有可预测的名字:如
“<service-name>.Contract”
Service StockQuoteService
In Message Request(Ticker As String)
Out Message Response(Quote As Money)
End Service
然而,为所有这些方便性导出合约造成缺少抽象。
如将会明白,合约用于精确地解释在该协议中将进行什么。导出的合约虽然始终正确(只要服务实现正确),但不具有可读性和易于使用的保证。它直接地反映该服务的内部结构,它对于试图装配客户机的程序员是很难理解的。还存在某些情况,编译器在处理时有困难,并且这会导致非常自由的合约。
服务一旦被启动,就能够域调用代码并行地执行,不管它们是否在同一应用程序域中。注意,没有对并发性规定任何内容,仅仅是并行性。这两者能被编写和实现为与彼此的控制流独立,只要它们仅仅是通过消息传递来同步的和共享数据的。注意,并不需要让不同的服务实例并发地执行(如,在单独的线程或进程中)或者它们不并发执行。
消息传递
鉴于以上讨论,转到时间表的概念,时间表能被定义为在其客户机方或者服务器方实现协议的代码的主体。有两种方式用于观察时间表:1)把它们看作发送和轮询消息的过程;或者2)详细观察它们的执行模型,它根本上不同于过程的执行模型。
正如在本说明书中以上所述,协议处理程序的自然实现将作为通过某一消息发送API发送消息,并使用另一API轮询消息的方法。在此常规模型中,每个服务实例必须具有其自己的执行线程,这是既不可伸缩也不允许环境的运行时调整的情况。传统上,每个服务实例需要一个单独的线程,并且对它不需要其它东西。操作系统(OS)确定哪个线程何时执行,并且不存在对应用程序操作者有效的调整控制。
这个常规方法的一个初始问题是它要求太多分配的线程,这加重了OS(例如,内核)资源的负担并且显著地增加了存储器的占用空间(footprint)。随着服务实例的来来往往,OS线程必须被创建和销毁,这是很昂贵的,并且不存在允许在一个时间仅仅保持如此多的线程活动的“压制(throttle)”(例如,要求每个服务实例一个线程)。
二进制代码中的低级位置是包含该对话状态的位置,这进一步复杂化了这一问题。因此,把服务实例移至另一个机器或只是简单地检测对话状态都是很困难的。
这样,根据本系统和方法,有两个关于协议处理程序(如,服务)的任何合理实现的基本方面。第一,不可能存在关于该协议处理程序的执行环境的假设。每个处理程序可能有一个线程,或者线程能在服务实例之间共享,即使它们具有重叠的生命周期。第二,对话状态可在数据中而不是在代码中表示。
虽然可假设第二个陈述与早先的关于声明对话状态的流程而非在数据中实现的讨论相冲突,仍预期开发者应当能够将对话流程视为可声明的。运行时模型仍然需要是对话状态在数据中表示的模型。换言之,尽管“自然”模型作为协议处理程序的源(如,编程人员)观点是适当的,但是将会理解,该运行时模型能够是十分不同的。技术人员将理解,通过在这个差距之间架起桥梁,VB配合管理以实现高级生产率和可读性的提高。
下一步,时间表代码被编译成为单独的类,它包含该源时间表的所有的逻辑,但是被重新安排以支持不同的并发性模型。一旦启动该时间表,代码执行,直到暂停事件(也叫作“退让点”)出现。
一旦暂停事件出现,时间表停止执行,并且把控制返回给调度运行时启动它的地点,或者给最后恢复事件的地点。当前,仅有的恢复事件是消息到达、消息超时和等待语句完成。
暂停时间表之后,其线程被交还给运行库,以用于它所满意的任何事情,或者毁坏,这都取决于底层运行库的线程模型。将会理解,一个更加复杂的主存框架可能依赖于用于所有其活动的线程池,而更基本框架在每次它需要线程时创建一个新线程。时间表的代码无论如何都不能作出假设。注意,在非服务环境中的第一个暂停事件(例如,对方法的调用)能够导致使用的线程阻断,直到时间表完成。
当消息到达并被路由到一特定服务实例,并且发现有一暂停接收等待它时,一种保留接收语句之后的主体的方法能够由运行库调用。现在这个调用点成为新的恢复地点(也称为“延续点”),并且该调用并不返回,直到时间表再一次暂停。
换言之,时间表被实现为带展示为数据的对话状态的回叫,但是该代码仍然表现为轮询代码,其中对话的流程使用示例性VB.Net的类似的语言构造来声明。
根据本发明,可作出适当的语言附加,用于消息处理和时间表中的并行性,但是大多数情况下,该时间表代码将由正常的VB语句组成:从循环和if语句到异常阻塞和变量声明的任何语句。
发送和接收消息
任何协议代码中的大多数基本操作,不管是在客户机还是在服务器方,都在连接的一个端点发送消息并在另一端点接收它。
在从一开始的操作中,在接收消息之前,它必须被发送,并且用于如此做的句法是非常直接的:
Dim qteSrvceConnector As CtRequestResponse=...
Send To qteSrvceConnector Request(″MSFT″)
请求/响应合约已在早先描述。发送语句能够具有一个某种类型的连接器。如果时间表是服务而且发送在主连接器上操作,则该连接器是稳含的。在这个例子中,客户机第一次连接到服务。
注意,不同于先前的服务客户机调用的情况,发送语句并不返回值。将会理解,该异步消息传递的整个要点是使得所有的数据传递都是明确的,因此任何双向通信在每个端点上都要求发送和接收。
在服务方,即实现方,该消息将以这种方式处理:
Receive Request(Ticker As String)
注意,在这个语句中不存在连接器引用。服务器将在其主连接器上接收消息。为了回复,服务将根据以下合约定义发送回结果作为串值,:
Send Response(″75.57″)
除了股票报价服务是完全不可靠的,并且在其数据采集策略中相当静态的事实之外,这是对它所存在的所有。现在,消息必须在另一端点接收。如所示,声明了消息参数将到接收语句之外的变量。
Dim Value As String
Receive From qteSrvceConnector Response(Value)
以上例子是完整的基于时间表的应用程序的表示。以下是在某一处的完整的代码:
Contract CtRequestResponse
In Message Request(Question As String)
Out Message Response(Answer As String)
Pattern
Request Then Response→End
End Pattern
End Contract
Service StockQuoteService
Implements CtRequestResponse
Sub Run()
Receive Request(Ticker As String)
Send Response(″75.57″)
End Sub
End Service
Public Class StockQuotesRUs
Public Function StockQuoteClient(tcr As String,addr As Uri)As String
Dim Value As String
Dim qteSrvceConnector As CtRequestResponse=_
CType(addr,CtRequestResponse)
Send To qteSrvceConnector Request(Ticker)
Receive From qteSrvceConnector Response(Value)
Return Value
End Function
End Class
将会理解,通过在该文件的顶部添加诸如“Option Strict On”(如,VB配合当前要求严格模式的编译)等语句,或者通过添加某些应用程序代码以调用服务客户机,该代码能够执行而无需进一步的配置。该代码能够在“示例-1”目录下找到。
根据这些语句的执行模型,当接收语句被执行时,并且没有与它匹配的消息可用,则该时间表将暂停,例如放弃它正使用的线程。如果消息可用,它将不暂停,而执行将在下一行上立即继续。发送语句从不暂停-一旦消息被底层系统接受,该语句终止。
当信息到达时间表时,如果运行库发现时间表被暂停等待该类型的消息,那么该时间表通常在与运行库正使用的相同线程上立即恢复。
可能不正确地相信这对于所有能以合约语言表达强大的协议并不是足够的。例如,在系统等待各种消息同时到来,例如从同一状态出来的两个转移,或带有多个无序消息或分裂合约定义的触发器的情况。
为处理这些替换的转移情况,VB配合定义一种“Select”语句的变体,来处理接收一组消息之外的消息的情况:
Select Receive
Case Receive Request(str As String)
Case Receive ItsOver1()
End Select
根据本一代码,两个消息“Request”和“ItsOver1”中的仅仅一个将在这个语句中接收。然而,如果想要接收A和B(按任何次序)或者只是C,如下所示:
State_1:
A And B→State_2
C→State_3
以下代码能够实现这个目标:
Select Receive
Case Receive A(),Receive B()
Case Receive C()
End Select
特别地,通过在同一行上放置接收语句,如在“select Receive”中的同一“Case”语句中,能指定它将在继续之前按某种未定义的次序接收所有消息。首先实现所有其传入消息传递需求的情况是最终被选择的情况。直到关注select语句,如果C在A之后但在B之前来到,那么A成为可用,以在未来的某个其它的点上接收。
现在转到实现CtRepeatedRequestResponse合约:
Contract CtRepeatedRequestResponse
In Message Request(Question As String)
Out Message Response(Answer As String)
In Message ItsOver1()
Out Message ItsOver2()
Pattern
Start:
CtRequestResponse→Start
ItsOver1 Or ItsOver2→End
End Pattern
End Contract
Service StockQuoteService
Implements CtRepeatedRequestResponse
Sub Run()
Do
Select Receive
Case Receive Request(Ticker As String)
Send Response(″75.57″)
Case Receive ItsOver1()
Exit Do
End Select
Loop
End Sub
End Service
Public Class StockQuotesRUs
Public Sub GetStockQuotes(addr As Uri)
Dim qteSrvceConnector As CtRepeatedRequestResponse=_
CType(addr,CtRepeatedRequestResponse)
Dim cmd As String=″″
Console.WriteLine(″Enter ticker symbol:″)
cmd=Console.ReadLine()
While cmd.ToLower<> ″quit″
Send To qteSrvceConnector Request(cmd)
Receive From qteSrvceConnector Response(Value As String)
Console.WriteLine(″Quote for″″& cmd &″″:″& Value)
Console.WriteLine(″Enter ticker symbol:″)
cmd=Console.ReadLine()
End While
Send To qteSrvceConnector ItsOver1()
End Sub
End Class
这里存在若干值得注意的方面,首先,示出了行动中的“select Receive”语句,但是它也是在常规的“Do”循环语句中嵌套的。存在极少关于时间表的包含物的限制(如,“语言限制”在下文讨论),而循环并不在其中。在服务客户机中,围绕发送/接收使用循环,它是系统与允许一次又一次地请求股票价格的用户交互之处。这个代码在DVS例子中作为示例-2可用。
根据这个例子,服务客户机在冒险。例如,假定当服务认为是恰当时(例如在整个一分钟内未从客户接收到任何内容)发送一个“ItsOver2()”消息。这里,服务客户机还能使用“select Receive”语句来处理这种可能性。
服务能够检测到已经整个一分钟没有任何消息。例如,服务可能判定这是表示客户机可能已经离开而没有发送ItsOver1消息的坏信号。这就需要处理这种情况的某一方法。如上所讨论的,会话超时是很有用的,但是往往太激烈。
相反,可以在“Select Receive”语句上放置超时间隔,并且取得对于信息不到达的场合更精细的控制:
Service StockQuoteService
Implements CtRepeatedRequestResponse
Sub Run()
Do
Select Receive Within TimeSpan.FromMinutes(1)
Case Receive Request(Ticker As String)
Send Response(″75.57″)
Case Receive ItsOver1()
Exit Do
Case TimeOut:
Send ItsOver2()
Exit Do
End Select
Loop
End Sub
End Service
间隔的类型能够是System.TimeSpan。以上“FromMinutes”方法调用是该类型上的共享方法之一,它允许从一特定时间单位(例如,秒、分、小时)构造TimeSpan(时间跨度)。参照.NET框架文献以获得关于System.TimeSpan的更多细节。
“select Receive”语句的定义是如果TimeSpan是0,那么,如果一个超时分支不能够被立即从已经接收到的消息中得到满足并且正在等待,则采用该超时分支。换言之,这是是确保该选择接收语句的确未暂停的一种方法。它允许时间表对某些消息进行快速核查,如果它们可用则应当被立即处理;如果不是,那么有更好的事情让时间表做。
注意,以上例子并不只是让会话超时并且让服务实例离开:相反,它们具有发送“ItsOver2”消息给客户机的机会,通知客户机,从而等待慢速用户将会表现得非常好。这是成为一个好的“服务公民”(例如,通知它的合伙人它将离开)的一部分。
分裂行
尽管“select Receive”足以支持替换方案以及触发信号状态转移的无序消息,但当支持合约中的分裂时是不方便的。在这个例子中,假定合约模式是:
State_1:
A→State_2,State_3
State_2:
B→State_4
State_3:
C→State_4
在接收方,这使得能够以以下方式编码:
Dim gotA As Boolean=False
Dim gotB As Boolean=False
Dim gotC As Boolean=False
While Not gotA AndAlso Not gotB AndAlso Not gotC
Select Receive
Case Receive A()When Not gotA
″Handle A
gotA=True
Case Receive B()When Not gotB
″Handle B
gotB=True
Case Receive C()When Not gotC
″Handle C
gotC=True
End Select
End While
注意,所有这些簿记必须完成,以跟踪哪些消息已经被接收(并且因此应该不再看到)。示例性VB配合处理这种类型的情况。例如,
Split
Line
Receive A()
″Handle A
Line
Receive B()
″Handle B
Line
Receive C()
″Handle C
End Split
在此,不存在任何簿记代码。换言之,每一消息接收方能够被独立于其它消息接收方而处理。
“split”语句能够提供具有并行性的VB配合,而不需要关注并发性的复杂性。以上语句的意思是,“split”中的三个嵌套的块,被称为分裂行,能够相互独立地,例如并行地执行。
然而,它们没有并发地执行,并且其管理是通过先前描述的暂停和恢复过程来看管。分裂行不可被恢复,直到时间表中的所有其它行都被暂停。
当执行“split”时,所有其分裂行最初被暂停,并且然后按按声明的次序恢复分裂行。这意味着只要第一个分裂行没有暂停,它就是在时间表中正在执行的仅有的分裂行。换言之,分裂行的同步是隐含的,且通过暂停来控制。不需要担心锁定对象或者在分裂行之间引入缓冲区。相反,可以理解,在暂停事件之间,当前的分裂行是正在运行的仅有的行。
事实上,可以绝对地避免将任何格式的其自己的线程引入到时间表中。这样做将破坏作为根据本发明的时间表的基础的模型。
现在转到描述如何使用分裂行来实现上述重复股票报价客户机的方面:
Public Class StockQuotesRUs
Public Sub GetStockQuotes(addr As Uri)
Dim qteSrvceConnector As CtRepeatedRequestResponse=...
Dim value As String
Split
Line
Dim cmd As String=″″
Console.WriteLine(″Enter ticker symbol:″)
cmd=Console.ReadLine()
While cmd.ToLower<>″quit″
Send To qteSrvceConnector Request(cmd)
Receive From qteSrvceConnector Response(Value)
Console.WriteLine(″Quote for″″& cmd &″″:″& value)
Console.WriteLine(″Enter ticker symbol:″)
cmd=Console.ReadLine()
End While
Send To qteSrvceConnector ItsOver1()
Exit Split
Line
Receive From qteSrvceConnector ItsOver2()
Exit Split
End Split
End Sub
End Class
注意,第一个分裂行与前面描述的实现相同。注意,所有完成的是添加了“ExitSplit(退出分裂)”。这是通用“Exit”语句的另一变体,它终止围绕“split”语句的所有分裂行。第二个分裂行等待“ItsOver2”消息,它也可能永不到达。如果不是,那么第一行将终止语句。
配合类
回过头来参考常规时间表和出现在配合类(如果该类存在)中的时间表之间的差别:
Public Class StockQuotesRUs
Public Sub GetStockQuotes(addr As Uri)
Dim qteSrvceConnector As CtRepeatedRequestResponse=...
Dim value As String
Split
Line
Dim cmd As String=″″
Console.WriteLine(″Enter ticker symbol:″)
cmd=Console.ReadLine()
While cmd.ToLower<>″quit″
Send To qteSrvceConnector Request(cmd)
Receive From qteSrvceConnector Response(Value)
Console.WriteLine(″Quote for″″& cmd &″″:″& value)
Console.WriteLine(″Enter ticker symbol:″)
cmd=Console.ReadLine()
End While
Send To qteSrvceConnector ItsOver1()
Exit Split
Line
Receive From qteSrvceConnector ItsOver2()
Exit Split
End Split
End Sub
Public Function GetStockQuote(addr As Uri,ticker As String)As String
Dim qteSrvceConnector As CtRepeatedRequestResponse=...
Send To qteSrvceConnector Request(ticker)
Receive From qteSrvceConnector Response(Value As String)
Send To qteSrvceConnector ItsOver1()
Return value
End Function
End Class
可以用以下方式分解:
Public Class StockQuotesRUs
Public Sub GetStockQuotes(addr As Uri)
Dim qteSrvceConnector As CtRepeatedRequestResponse=...
Dim value As String
Split
Line
Dim cmd As String=″″
Console.WriteLine(″Enter ticker symbol:″)
cmd=Console.ReadLine()
While cmd.ToLower<>″quit″
Value=GetSingle(qteSrvceConnector,cmd)
Console.WriteLine(″Quote for″″& cmd &″″:″& value)
Console.WriteLine(″Enter ticker symbol:″)
cmd=Console.ReadLine()
End While
Send To qteSrvceConnector ItsOver1()
Exit Split
Line
Receive From qteSrvceConnector ItsOver2()
Exit Split
End Split
End Sub
Public Function GetStockQuote(addr As Uri,ticker As String)As String
Dim qteSrvceConnector As CtRepeatedRequestResponse=...
Dim value As String=GetSingle(qteSrvceConnector,ticker)
Send To qteSrvceConnector ItsOver1()
Return value
End Function
Private Function GetSingle(ctor As CtRepeatedRequestResponse,_
ticker As String)As String
Send To ctor Request(ticker)
Receive From ctor Response(value As String)
Return value
End Function
End Class
问题是由于每个方法具有它自己的时间表,“GetSingle”调用将被阻断直到完成。换言之,在第一个分裂行的内部,当等待响应消息时该调用没有退让,并且第二行没有开始执行。本质上,它将被饿死(starved)。如果“GetSingle”内部的Receive语句能够与第二行中的语句协调,那么这个问题能够被减轻。这恰好就是配合类实际实现的。
通过简单地改变类声明成:
Public Orchestration Class StockQuotesRUs
能够实现所要求的行为。换言之,GetSingle中的Receive语句退让意味着该分裂行退让且第二行可以执行。
等待
时间表内部的另一个基本操作是等待一段设定的时间。存在一个长期运行服务的完整类,其中其时间的很大一部分被花费在延迟上(通常被称为“拖延器(Procrastinator)”模式)。通常有监视服务,它通常倾听命令(例如,像关机、添加监视器等),并且在同时能够向其它服务发送心跳(heart-beat)信号或心跳轮询请求。现在参考这些概念以建立公用术语(如果不为其它原因)。
当设计期望运行扩展的时间段的某些服务/服务类时,通常必须在某一类型的接口中设计,这允许其它服务自动解决而不管服务是否仍然可用,并且抢先完成。一种方法是让有困难达到服务的所有客户机发送信息到某个中央服务,但是由于向其设计添加了使用情况(对于客户机的原始目的没有任何需要做的事情的使用情况)而使得客户机比所需的更加复杂。
相反,创建一监视服务,它通过监听其心跳来跟踪第一个服务是否可用,心跳是以某种频率发送到监视服务的消息。
一种典型的设计允许监视服务要求被监视的服务发送它自己的心跳一次,或者每N秒种开始发送它,或者告诉监视器它每隔多久准备发送心跳。然后监视服务可能够使用等待某些时间并发送单拍心跳(single-shot-heart-beat)请求的循环,或者要求被监视的服务规则地发送心跳。
在后一种情况下,监视服务将使用“selectReceive”并且设置超时间隔为同意的间隔稍长的某一间隔。如果心跳到达,则每件事都正常,但是如果该语句超时,那么就是某些有害东西正在进行的指示,并且监视服务能够发送出某种形式的警告。
将会理解,一个更复杂的实现可在心跳消息上捎带(piggy-back)其它信息,这允许操作员得到长期运行服务的统计量。
如果Wait没有被明确地支持作为配合中的语句,则引入固定时间延迟可能对于应用程序的可伸缩性具有灾难性结果。可能必须依赖于Thread.Sleep(),它是.NET框架中的一个API,但它是完全不适用服务的执行模型,该模型要求并发性由主存环境的运行库管理。调用Thread.Sleep()将在等待持续时间内占用当前线程。对于循环调用Wait的服务,这可以是不确定的。
换言之,这一服务将一个线程都分配给它自己。应该理解,这是与大部分时间不被使用的线程有关(例如,一个典型的监视线程可能花费100微秒来做工作,并且然后继续睡眠几分钟。睡眠一分钟并工作100微秒的线程将花费其时间的99.9998%来睡眠)。
因此,非常重要的是在该模型中考虑对延迟需求。这是用“Wait”语句来完成的。
Wait TimeSpan.FromMinutes(1)
为示例它在监视服务中的使用(该服务未监听命令),以下是一个适当的时间表:
Service Client MonitorServiceAt(serviceRef As Uri)
Schedule
Dim monitoredService As HeartBeat=HeartBeat(serviceRef)
Dim period As TimeSpan=TimeSpan.FromSeconds(30)
Do
Dim before As DateTime=DateTime.Now
Send To monitoredService AreYouAlive()
Receive From monitoredService Pulse()
Wait(before+period-DateTime.Now)
Loop
End Schedule
End Service
注意,该系统能够补偿它执行循环主体所化费的时间。这是要确保等待时间段尽可能接近指定的时间。
Wait语句的操作数能够是System.TimeSpan类型。
动态并行性
尽管Split语句能够允许代码主体定义,但没有办法使用它来并行地执行n行代码。这种动态行为的情况是很强烈的,并且以两种方式被支持:
1.Split-For-Each语句
2.Split-Select-Receive语句
前者具有与For-Each语句同样的基本语义,除了它与所有其它迭代并行地执行循环的每个迭代之外。换言之,存在与集合中被迭代的项目一样多的执行并行行。一旦这个数量被确定,就不能够把另外的行添加到该并行语句中。Split-For-Each具有与Split同样的“加入”规则:当所有的迭代完成时,Split-For-Each语句也完成。作为例子:
Split For Each itm As SKU In skuList
Dim warehouse As WarehouseInventory=...
Send To warehouse Allocate(itm)
Receive From warehouse Confirmation
Next
对于包含这种语句的正常的For-Each的好处是所有的Receive语句并行地等待。在发送和接收之间的延迟愈大,能够这样做也就变的愈重要。
“split-Select-Receive”能够用于响应于传入的消息动态地启动并行执行的新行。一种变体取决于对多路复用会话的理解,但较简单的变体是能帮助实现这个合约的变体(A、B、和C都是传入消息):
S1:
A→S1 And S2
C→S3
S2:
B→S3
S3:
这里,该合约允许采取任何数量的交叉的<A,B>序列,直到得到C,在此它等待所有B的到来。由于每个<A,B>序列独立于所有其它序列来处理,因此使用循环中的Select-Receive语句将很难实现。然而,能够使用Split-Select-Receive:
Split Select Receive
Case Receive A()
Receive B()
Case Receive C()
Finish Split
End Select
每次语句中的一行退让,可创建新的一行,并且可对该新行执行选择语句。这意味着语句将从不独立地完成(例如,将始终存在一个新的、新生的执行行,以处理下一个消息)。为退出该语句并仍然允许所有已经启动的行执行,语句中的某些代码将执行“Finish Split(完成分裂)”语句。
注意:鉴于前述内容,将会理解,执行行的概念并不指源代码行,但是却指“执行的行”,例如,并行程序中的语句序列。
退出
“Exit”的若干新版本已经在上文展示,但是为了便于理解,关于它们是什么以及他们做什么的重述在下文提供:
Exit Split
这将终止围绕“split”、“split-For-Each”或“split-Select-Receive”的所有退让行,并且转移到对应的“End”或“Next”语句。
Exit Line
这将终止当前行(不管静态还是动态),但是不影响任何其它行。
过滤器
当在前面讨论“Receive”语句时,没有着眼于消息过滤器。类似于把子句放置在异常处理程序上以使处理程序匹配不只是基于异常的类型,能够在接收语句上添加子句,它允许基于某些特殊因素,如其内容或全局状态,来接受或拒绝消息。
注意,因为合约语言不具有指定对应条件的手段,因此使用过滤器表达式来拒绝消息的时间表能够确保该合约在进程中没有被违反。
采用过滤器是很方便的,尤其是在某些情况中(但不在其它情况中)当能够接收更多消息时。例如,
Select Receive
Case Receive A(s1 As String)When s1=″ACK″,B(s2 As String)
″Handle situation
Case Receive A(s3 As String)When s3=″OK″
″Handle situation
End Select
仅当该消息参数具有值“OK”,这个语句将接受消息A,否则如果A消息参数具有值“ACK”,它将接收A和B。如果这些情况都非真,那么语句等待直到它们是真。在该消息被消耗之前,过滤器可如此被调用多次,所以至关重要的是它是没有副作用的真表达式。
次要连接器
如上指出,当服务首次被启动时,它被分配一个连接器,该连接器能够用于发送和接收消息,而无需明白地提及。这个连接器被称为该服务的“主”连接器。主连接器被绑定到客户机连接器,在客户机连接器上第一个消息被发送到服务。它能够始终具有其合约上的实现观点。
正像任何其它客户机一样,当被激活的服务希望作为客户机与其它服务通信时,它可以使用绑定到某一服务地址的连接器工厂、从该工厂创建连接器并且开始发送消息。如上所表明,由连接器工厂创建的所有连接器具有使用观点。
如果服务希望允许其它客户机启动与它的对话,而不管是在与主连接器相同的合约上还是在某一其它合约上,将会怎样?主连接器已被绑定,因此共享它不是一个选项。另外,如果想要实行某一其它合约,主连接器可能根本无用。
回答是应当创建带实现观点的次要连接器。服务中所有实现观点连接器都被称为次要连接器。将会理解,在服务的外部,所有连接器是相同的——不管是主连接器还是第二连接器。
创建一个新的、次要的实现连接器被表示如下:
Dim SecondaryConnector As Implements RequestResponse=CtRequestResponse()
在此值得注意两件事:
1.能够使用“Implements”关键字作为变量声明中的类型修饰词。这给被声明的变量实现观点。
2.实际的连接器对象能够通过调用所要求的合约来创建,而没有服务引用。这是类型名字一种相当奇怪的使用,但它是创建次要连接器的仅有的方式。
“Implements”合约类型修饰词能够在创建连接器变量的任何时候使用,包括作为类或者模块成员,或者作为方法的参数。试图分配一个观点的对象给相反观点的变量在编译时会失败。注意,连接器变量分配规则能够是完全与连接器到连接器绑定规则相反。换言之,连接器只能够被分配同样观点的连接器,但它们必须被绑定到相反观点的连接器。
服务未找到
存在发现服务不可用的两种方法。第一,TCP地址可能无效。换言之,端口未打开且服务实际上不在此处。这个问题在试图发送第一个消息到该服务时被检测到,并且会立即引发异常。
第二,消息能够到达目的地,但是主存环境未识别服务名或者服务实例。在此情况下,发送消息的时间表继续其生命,并且抛出异常是没有意义的。
该问题相反通过覆盖规划类中的方法来处理。
Protected Overrides Sub OtherSideNotThere(ByVal sref As String)
End Sub
超时
当从一个时间表发送异步消息到另一个时,不存在响应将被接收到的保证。有很多将发生这一情况的理由,包括但并不限于,低级通信失败,或者在客户和服务之间在期望什么的方面不协调。合约能够指定两个消息之间的最大延迟,从而不响应合约要求了两个星期的请求的服务在技术上并不违反合约。仅当它的时间表被终止而没有首先发送消息时,违反了合约。
这就提出了一个问题,必须考虑何时为服务和服务客户机编写时间表。服务在接收时能够依赖的仅有消息是第一个消息,因为该消息的到达是它在第一位置上被启动的原因。服务客户机甚至不能太多地依赖于它。所幸的是,VB配合提供适当的实现工具。
存在要求分别处理的两种情况:
1.如果给定的消息没有到达,则该时间表实施的计算过程能够被完全放弃。对于这种情况,会话超时是恰当的,而且是被推荐的,因为它抑制了时间表代码的扩张。
2.如果给定的消息没有到达,则该时间表从其中恢复并以默认信息继续,或者试图重新发送该请求,或者联系某一其它服务以获取该信息。带有超时间隔的Select/Receive语句被推荐用于这种情况。这些情况早先已经被讨论过:
Select Receive Within TimeSpan.FromMinutes(10)
Case Receive Response(str As String)
Case TimeOut:
End Select
注意,如上所演示,Select/Receive语句并不必须具有多个情况。事实上,Receive语句是带单个情况的Select/Receive的更有效实现(但功能上等效)的版本。换言之,
Receive A(...)
等效于:
Select Receive
Case Receive A(...)
End Select
具有了这样的认识之后,应该清楚,在期望消息而没有到达的任何时刻,能够通过向接收语句添加超时间隔来处理它。
根据本发明,当这发生时,屈居于情况,若干种替换是可用的。然而,应该指出,一般不能够定义任何方针。每件事都完全依赖于正被解决的问题。例如:
·等待稍长一点时间;
·如果响应是缺少参数的“确认风格”消息,那么能够判定在确认是否定(或者在某些情况下,可能为肯定)的假设下继续。
·如果响应消息传输回可以没有它而继续的某些非关键数据,则或许就以未到达的数据的某些默认值如此进行。可能web服务正在从护照类服务中寻找个人的数据,但是如果不存在,则web服务以有限的个人数据继续;
·试图重新发送消息给正在等待它的服务;以及
·找出能够连接到的某一其它服务,或者同一服务的另一个实例,以取得该信息。
完成这些的方法当然随情况的不同而变化。除了以上列出的第一个项目之外,对所有的共同的是:如果合约本身并不适应某些消息还没有到达的事实,则在此情况下会存在很大的问题。当时间表存在时,在它的生命周期中创建的每个连接器将被检查,以验证通过它完成的信息交换是否已完成。任何“未完成”连接器将被注解为违反其合约,并且将抛出异常。当没有接收到应该有的消息时,则某处存在一个错误,并且运行库不能简单地忽略它,因为它没有办法知道对于时间表它是如何重要。然而,通过丢弃没有消息到达的连接器,时间表通知运行库:它应当不确认该特定连接器上的合约。
Select Receive Within TimeSpan.FromMilliseconds(100)
Case Receive From Connector_1 Response(str As String)
Case TimeOut:
Connector_1.Discard()
End Select
然而,丢弃连接器也可以意味着在该连接器上不能采取任何其它行动。不能发送给它,或者从它接收消息。如果被尝试,将出现错误。
注意,选择语句上的超时间隔并没有对一组语句执行所花费的总时间设定上限,仅仅是一个语句等待消息的时间。为做以上事情,能够利用分裂和等待语句:
Split
Line
Wait TimeSpan.FromMinutes(2)
Exit Split
Line
″Some timed activity of multiple statements.
End Split
由于VB配合没有声称任何实时行为,因此不存在这将花费不超过指定的两分钟的保证。仅仅当第二行达到暂停事件时,该分裂语句实际终止,并且“End Split”之后的语句执行。
与非时间表代码交互
存在使用发送和接收语句用于异步程序设计可能得不到正确解决方案的情况。在某些情况下,时间表的单线程阻断特性妨碍了第一流的解决方案,或者可能有具有仅仅本质上不是时间表驱动的某些UI代码的需要。
好消息是即使在这样的情况下,还是容易编写代码薄层,它将诸如按下按钮事件等某些事情转换成到服务或服务客户机的消息。简言之,异步程序设计与基于事件的图形UI程序设计相处融洽。
为实现在基于事件和基于时间表这两个范例之间的桥梁,VB配合允许非时间表代码发送和映射传入的消息到事件。因此消息能够被来回交换,而没有时间表和其阻断语义。
合约中的每一消息导致事件被添加入到该类型。由于方法和事件在.NET中不能重载,因此事件名是带有直接附加于它的串“Event”的消息名。该方法具有与消息完全相同样的名字。
作为例子:
Private Sub Foo()
Dim Connector_1 As CtRequestResponse=...
AddHandler Connector_1.ResponseEvent,AddressOf ResponseHandler
Send To Connector_1 Request(″Gimme!″)
End Sub
Private Sub ResponseHandler(str As String)
End Sub
在下文将检查的示例应用中,消息交换将以这种方式尤其相关于图形UI交互而使用。
图形用户界面
基于Win32的图形用户界面在想起什么线程用于完成特定的事情,例如向控件提供新数据以显示时,具有特殊的需求。不是所有的控件都具有这种需求,但有些的确具有,并且必须适应这点。时间表和个别的连接器都能够被附加到UI线程,从而在时间表中或在连接器上接收到的所有消息都在特定UI线程的上下文中接收。
这个特点很容易使用,但是应该限制在它确实是被要求的情况下。可以理解,将时间表代码强制到单一线程将限制并发性的机会,并因此是一个潜在的性能瓶颈。
Dim connector As Contract_1=...
Connector.SetControl(Form1)
第二个语句将确保一旦它返回,在这个连接器上接收的所有消息将达到该表单所拥有的线程。注意,也能够在服务的主连接器上完成这件事(这是明确地涉及到主连接器的少数几个合理原因之一)。
Primary.SetControl(Form1)
虽然最有可能,但服务和服务客户机并不直接调用UI代码,而是发送消息到包含它们的表单,这意味着最有可能,它是非时间表连接器,对于它设置控制线程是有意义的。
以下是当使用″setControl()″时的例子:
Select Receive
Case Receive From connector1 A(),Receive From connector2 B()
End Select
除非connector1和connector2都被绑定到同一拥有的线程,在这情况下,消息接收将是非确定性的(当考虑线程时)。可以理解,在该情况下,线程信息能够是从任一连接器中拉出。因此,重要的是确保在一种情况下所有的连接器被绑定到同一拥有的线程,或者时间表被绑定到拥有的线程。
可操作的计算环境
现在参照图7,示出了可用于执行所揭示的体系结构的计算机的框图。为了提供本发明的各方面的附加环境,图7及以下讨论旨在提供可在其中实现本发明的各方面的合适的计算环境700的简要概括描述。虽然上文在可运行于一台或多台计算机上的计算机可执行指令的通用的上下文中描述了本发明,但本领域的技术人员将认识到,本发明也能结合其它程序模块实现和/或作为硬件和软件的组合来实现。
通常,程序模块包括例程、程序、组件、数据结构等等,它们完成特定的任务或者实现特定的抽象数据类型。另外,本领域技术人员将明白,本发明的方法能够用其它计算机系统配置来实践,包括单处理器或多处理器计算机系统、小型计算机、大型计算机,以及个人计算机、手持计算设备、基于微处理器或可编程消费者电子产品等等,它们的每一个都能够被操作性地耦合到一个或多个相关设备。
所示的本发明的方面还能在分布式计算机环境中实践,其中某些任务由通过通信网络连接的远程处理设备完成。在分布式计算机环境中,程序模块能够位于本地和远程存储器存储设备中。
计算机通常包括各种计算机可读介质。计算机可读介质能够是能够被计算机访问的任何可用介质,而且包括易失和非易失的介质、可移动和不可移动介质。作为例子而非限制,计算机可读介质能够包括计算机存储介质和通信介质。计算机存储介质包括易失和非易失、可移动和不可移动介质,它们以任何方法或技术实现以用于例如计算机可读指令、数据结构、程序模块或其它数据等信息的存储。计算机存储介质包括但不限于,RAM、ROM、EEPROM、闪存或其它存储器技术、CD-ROM、数字视频盘(DVD)或其它光盘存储、磁盒、磁带、磁盘存储或其它磁存储设备、或者能用于存储所需的信息并能够由计算机访问的任何其它介质。
通信介质通常在诸如载波或其它传输机制等已调制数据信号中包含计算机可读指令、数据结构、程序模块或其它数据,并且包括任何信息传递介质。术语“已调制数据信号”指其一个或多个特征以在信号中编码信息的方式设置或改变的信号。作为例子但非限制,通信介质包括有线介质,例如有线网络或直接线路连接,和无线介质,例如声学、RF、红外和其它无线介质。任何以上的组合也应该被包括在计算机可读介质范围之内。
再次参照图7,示出用于实现本发明的各个方面的示例性环境700,包括计算机702,计算机702包括处理单元704、系统存储器706和系统总线708。系统总线708把包括但不限于系统存储器706的系统组件耦合到处理单元704。处理单元704能够是各种市场上可购买的处理器的任一种。双微处理器和其它多处理器体系结构也可被用作为处理单元704。
系统总线708能够是若干种总线结构中的任何一种,它可进而互连到存储器总线(带或不带存储器控制器)、外围总线和使用各种市场上可购买的总线结构的任一种的局部总线。系统存储器706包括只读存储器(ROM)710和随机存取存储器(RAM)712。基本输入/输出系统(BIOS)是存储在如ROM、EPROM、EEPROM等非易失存储器710中,该BIOS包含例如在启动期间帮助在计算机702的元件之间传输信息的基本例程。RAM 712也能够包括高速RAM,例如用于高速缓存数据的静态RAM。
计算机702还包括内部硬盘驱动器(HDD)714(如EIDE、SATA),该内部硬盘驱动器714也可被配置成在恰当的机箱(未示出)中外部使用;软磁盘驱动器(FDD)716,(如,读取或写入可移动磁盘718);以及光盘驱动器720(如,读取CD-ROM磁盘722或者读取或写入其它高容量的光介质,如DVD)。硬盘驱动器714、磁盘驱动器716和光盘驱动器720能够分别通过硬盘驱动器接口724、磁盘驱动器接口726和光盘驱动器接口728连接到系统总线708。用于外部驱动器实现的接口724包括通用串行总线(USB)和IEEE 1394接口技术的至少一个或二者。
驱动器和与它们关联的计算机可读介质提供了数据、数据结构、计算机可执行指令等的非易失存储。对于计算机702,驱动器和介质容纳适当数字格式的任何数据的存储。虽然以上计算机可读介质的描述涉及HDD、可移动磁盘和如CD或DVD等可移动光盘,然而本领域技术人员应该理解,可由计算机访问的其它类型的介质,如zip驱动器、磁带盒、闪存卡、盒式磁带等等,也可用在示例性操作环境中,并且进一步,任何这样的介质都可包括用于完成本发明的方法的计算机可执行指令。
若干程序模块能够被存储在驱动器和RAM 712中,包括操作系统730、一个或多个应用程序732、其它程序模块734和程序数据736。操作系统、应用程序、模块和/或数据的所有或部分也能够被高速缓存在RAM 712中。
可以理解,本发明能够用各种市场上可购买的操作系统或操作系统的组合来实现。
用户能够通过一个或多个有线/无线输入设备,如键盘738和诸如鼠标740等定点设备输入命令和信息到计算机702。其它输入设备(未示出)可包括话筒、IR遥控器、操纵杆、游戏垫、输入笔、触摸屏等等。这些和其它输入设备往往通过耦合到系统总线708的输入设备接口742连接到处理单元704,但也能通过其它接口连接,如并行端口、IEEE 1394串行端口、游戏端口、USB端口、IR接口等。
监视器744或者其它类型的显示设备也通过接口,如视频适配器746连接到系统总线708。除了监视器744之外,计算机通常包括其它外围输出设备(未示出),例如扬声器、打印机等。
计算机702可通过到一个或多个远程计算机(如远程计算机708)的有线和/或无线通信使用逻辑连接而在网络环境中操作。远程计算机708能够是工作站、服务器计算机、路由器、个人计算机、便携式计算机、基于微处理器的娱乐设备、对等设备或者其它公用网络节点,并通常包括上文相对于计算机702描述的许多或全部元件,虽然为简单起见,仅仅示出存储器存储设备702。所描述的逻辑连接包括到局域网(LAN)752和/或大型网络,如广域网(WAN)754的有线/无线连接。这样的LAN和WAN网络环境在办公室和公司中很常见,并促进了企业范围计算机网络,如内联网,所有这些都可连接到全球通信网络,如因特网。
当用在LAN网络环境中时,计算机702通过有线和/或无线通信网络接口或适配器756连接到局域网752。适配器756可促进到LAN 752的有线或无线通信,它还能包括在其上部署的无线接入点,以与无线适配器756通信。当使用在WAN网络化环境中时,计算机702能够包括调制解调器758,或者被连接到LAN上的通信服务器,或者具有用于通过WAN 754,例如因特网建立通信的其它装置。调制解调器758能够是内置或外置的,以及有线或无线的设备,它通过串行端口接口742被连接到系统总线708。在网络环境中,相对于计算机702所描述的程序模块或者其部分能够被存储在远程存储器/存储设备750中。可以理解,示出的网络连接是示例性的,并且也能使用在计算机之间建立通信链路的其它装置。
计算机702可用于与操作上部署在无线通信中的任何无线设备或实体通信,如打印机、扫描仪、台式和/或便携式计算机、便携数据助理、通信卫星、关联于无线可检测标签的任何设备或位置(如电话亭、报亭、休息室等)、以及电话。这至少包括Wi-Fi和BluetoothTM(蓝牙)无线技术。这样,通信能够是如同传统网络那样的预定义结构,或者简单地是至少二个设备之间的特别(ad hoc)通信。
Wi-Fi(或无线保真)允许从家中的床上、酒店房间的床上或工作时的会议室连接到因特网,而不用线。Wi-Fi是一种类似于蜂窝电话的无线技术,它使如计算机等设备能够在室内和室外发送和接收数据;在基站范围内的任何地方。Wi-Fi网络使用称为IEEE 802.11(a、b、g等)无线电技术来提供安全的、可靠的、快速的无线连接。Wi-Fi网络能够用于将计算机彼此连接、连接到因特网、以及连接到有线网络(使用IEEE 802.3或以太网)。Wi-Fi网络在未许可的2.4和5GHz无线电波段操作,带有11Mbps(802.11b)或者54Mbps(802.11a)数据速率或者带有包含二种波段的产品(双波段),因而该网络能够提供类似于用在很多办公室中的基本10BaseT有线以太网络的真实性能。
现在参照图8,示出了个根据本发明的示例性计算环境800的原理框图。系统800包括一个或多个客户机802。客户机802能够是硬件和/或软件(如,线程、进程、计算设备)。客户机802能够通过使用本发明容纳cookie和/或关联的上下文信息。系统800还包括一个或多个服务器804。服务器804也能够是硬件和/或软件(如,线程、进程、计算设备)。服务器804能够通过例如使用本发明容纳线程以完成转换。在客户机802和服务器804之间的一种可能的通信能够是适用于在两个或多个计算机进程之间传输的数据分组的形式。数据分组可包括例如cookie和/或关联的上下文信息。系统800包括通信框架806(例如,如因特网等全球通信网络),它能够用来促进客户机802和服务器804之间的通信。
通信能够通过有线(包括光纤)和/或无线技术来促进。客户机802可操作性地连接到一个或多个客户机数据存储808,它能够用来存储对客户机802本地的信息(如,cookie和/或相关联的上下文信息)。类似地,服务器804可操作性地连接到一个或多个服务器数据存储810,它能够用来存储对服务器804本地的信息。
文献A描述本发明的各个方面,并且这个文献被认为是本申请的详细说明的一部分。
以上讨论的内容包括本发明的例子。当然,不可能为描述本发明的目的而描述组件和方法的每一个可想到的组合。但是本领域的普通的技术人员能认识到,本发明的很多进一步的组合和交换是可能的。因此,本发明旨在包涵落在所附权利要求的精神和范围之中的所有这些替换、修改和变化。此外,在术语“包括”被用于详细描述或者权利要求之中的意义上,这样的术语意在以与术语“包含”在权利要求中被用作为过渡词汇时所解释的类似方式是包含性的。