最近在看Cricket这个实现了Actor模式的F#开源框架,对其工作方式作了一番探究。首先来看一段简单的例子代码:

 type Say = | Hello
let greeter =
actor {
name "greeter"
body (
let rec loop() = messageHandler {
let! msg = Message.receive()
match msg with
| Hello -> printfn "Hello" return! loop()
}
loop())
} |> Actor.spawn

先是定义了消息类型Say,接着通过computation-expression(计算表达式)的方式定义了greeter这个actor。actor计算表达式的定义见ActorConfigurationBuilder,包括body、name等都通过CustomOperationAttribute的方式给出了定义。比如body:

         member __.Body(ctx, behaviour) =
{ ctx with Behaviour = behaviour }

这里的behaviour即是上述例子中"body语法字"括号中的代码块。它构建了新的ctx:ActorConfiguration<'a>,即这里的ActorConfiguration<Say>。可以预见,behaviour作为一个被缓存的行为,必定在将来一个合适的时机被调度执行。在这之前,还是先看下代码块中的具体执行内容。

messageHandler又是一个计算表达式,定义在MessageHandlerBuilder中。这里主要是看下let!与return!的定义。先看let!:

         member __.Bind(MH handler, f) =
MH (fun context ->
async {
let! comp = handler context
let (MH nextComp) = f comp
return! nextComp context
}
)
member __.Bind(a:Async<_>, f) =
MH (fun context ->
async {
let! comp = a
let (MH nextComp) = f comp
return! nextComp context
}
)

这里MH的定义为:type MessageHandler<'a, 'b> = MH of ('a -> Async<'b>)。这里需要反复强调的是,async声明只是被转换为Async.Bind()这种形式的函数调用,并不代表任何对象。至于返回Async<'b>,那是因为Async.Bind()函数本身返回Async<'b>对象,即AsyncBuilder加工处理的中间对象。不止async,任何计算表达式都是如此。

Message.receive()的定义为:

     let receive() = MH (fun (ctx:ActorCell<_>) -> async {
let! msg = ctx.Mailbox.Receive()
ctx.Sender <- msg.Sender
ctx.ParentId <- msg.Id
ctx.SpanId <- Random.randomLong()
traceReceive ctx
return msg.Message
})

由Bind的定义可以看到,它包装了参数handler并返回新的MH-handler。我开始一直认为,Bind函数中会解析从Message.receive()的返回值,并交给后续代码块处理。但是这里却是返回了一个新的MH-handler,令人百思不得其解。事实上这依然是一个缓存的行为。我们可以把代码展开:

      let rec loop() =
// Message.receive()返回的MH-handler
let msgHandlerReceive = MH (fun (ctx:ActorCell<_>) -> async {
let! msg = ctx.Mailbox.Receive()
ctx.Sender <- msg.Sender
ctx.ParentId <- msg.Id
ctx.SpanId <- Random.randomLong()
traceReceive ctx
return msg.Message
})
// 匹配msg并处理的代码块
let funCodeBlock = fun (msg:Say) ->
match msg with
| Hello -> printfn "Hello" let MH(leftCodeBlock) = loop()
// return! loop() => MessageHandler.ReturnFrom(loop())
MH(fun ctx ->
traceHandled ctx;
leftCodeBlock(ctx)) // let!中的处理,返回新的MH-handler(粘合receive和codeBlock,而codeBlock中会通过return!返回新的MH-handler由Async异步递归处理)
MessageHandler.Bind(msgHandlerReceive, fun codeBlock ->
MH (fun context ->
async {
let! comp = msgHandlerReceive context
let (MH nextComp) = codeBlock comp // 用户代码块返回新的MH-handler
return! nextComp context
}
) )

注意上述MessageHandler.Bind调用时传入的codeBlock即为funCodeBlock,也就是用户代码。这里可以清楚地看到loop()事实上是通过嵌套调用MessageHandler.Bind(各种do!和let!以及return!)构建返回了一个个新的MH-handler,将message接收、解析、用户代码处理等串联起来,当调用loop()时(也就是将来调用ActorConfiguration<Say>.Behaviour时)返回一个串联后的MH-handler,再在合适的时机加以执行。至此,整个流程已经清楚,剩下的就是搞清楚何时执行behaviour的问题了。在构建ActorConfiguration<Say>结束后将由Actor.spawn处理,会创建Actor对象,并在创建中通过Async.Start执行如下代码:

do! MessageHandler.toAsync ctx defn.Behaviour

这里defn.Behaviour即是当初串联而来的MH-handler,ctx即为ActorCell<Say>。再看下MessageHandler.toAsync就一目了然了:

let toAsync ctx (MH handler) = handler ctx |> Async.Ignore

接收ActorCell<Say>对象ctx并执行流程。

BlueSea笔记<1>--Cricket初探的更多相关文章

  1. spring揭秘 读书笔记 一 IoC初探

    本文是王福强所著<<spring揭秘>>一书的读书笔记 ioc的基本概念 一个例子 我们看下面这个类,getAndPersistNews方法干了四件事 1 通过newsList ...

  2. 【驱动笔记9】初探IRP

    文章作者:grayfox作者主页:http://nokyo.blogbus.com原始出处:http://www.blogbus.com/nokyo-logs/34005738.html 此前我们可能 ...

  3. Linux内核笔记--网络子系统初探

    内核版本:linux-2.6.11 本文对Linux网络子系统的收发包的流程进行一个大致梳理,以流水账的形式记录从应用层write一个socket开始到这些数据被应用层read出来的这个过程中linu ...

  4. Spring笔记之(一)初探

    对spring框架的学习我是从模拟它的简单实现开始,这样也易于领悟到它的整个框架结构,以下是简单实现的代码: 配置文件:spring.xml <?xml version="1.0&qu ...

  5. angular2 学习笔记 ( 4.0 初探 )

    目前是 4.0.0-rc.2. 刚好有个小项目要开发,就直接拿它来试水啦. 更新 cli 到最新版, 创建项目, 然后 follow https://github.com/angular/angula ...

  6. Python之路【第二十三篇】:Django 初探--Django的开发服务器及创建数据库(笔记)

    Django 初探--Django的开发服务器及创建数据库(笔记) 1.Django的开发服务器 Django框架中包含一些轻量级的web应用服务器,开发web项目时不需再对其配置服务器,Django ...

  7. Python源代码剖析笔记3-Python运行原理初探

    Python源代码剖析笔记3-Python执行原理初探 本文简书地址:http://www.jianshu.com/p/03af86845c95 之前写了几篇源代码剖析笔记,然而慢慢觉得没有从一个宏观 ...

  8. 深度学习课程笔记(十一)初探 Capsule Network

    深度学习课程笔记(十一)初探 Capsule Network  2018-02-01  15:58:52 一.先列出几个不错的 reference: 1. https://medium.com/ai% ...

  9. 初探C++运算符重载学习笔记&lt;2&gt; 重载为友元函数

    初探C++运算符重载学习笔记 在上面那篇博客中,写了将运算符重载为普通函数或类的成员函数这两种情况. 以下的两种情况发生.则我们须要将运算符重载为类的友元函数 <1>成员函数不能满足要求 ...

随机推荐

  1. Linux 下 GCC 编译共享库控制导出函数的方法

    通过一些实际项目的开发,发现这样一个现象,在 Windows 下可以通过指定 __declspec(dllexport) 定义来控制 DLL(动态链接库)中哪些函数可以导出,暴露给其他程序链接使用,哪 ...

  2. 2017-10-23学大伟业Day1

    T1 叉叉 题目名称 叉叉 程序文件名 cross 输入文件名 cross.in 输出文件名 cross.out 每个测试点时限 1秒 内存限制 128MB 测试点数目 10 每个测试点分值 10 是 ...

  3. linux下查看隐藏文件

    linux下查看隐藏文件的快捷键:Ctrl+H 命令:ls -a

  4. [Bzoj3206][Apio2013]道路费用(kruscal)(缩点)

    3206: [Apio2013]道路费用 Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 536  Solved: 252[Submit][Status ...

  5. 51NOD 1424 零树

    Discription 有一棵以1为根的树,他有n个结点,用1到n编号.第i号点有一个值vi. 现在可以对树进行如下操作: 步骤1:在树中选一个连通块,这个连通块必须包含1这个结点. 步骤2:然后对这 ...

  6. 果皇的矩阵[matrix]

    #1101. 果皇的矩阵[matrix] 题目描述 输入格式 一行两个数,表示 N,M. 输出格式 一行一个数,表示答案对 10^9+7 取模后的结果 样例 样例输入 3 3 样例输出 38 数据范围 ...

  7. [转] oracle里long类型的总结

    1.LONG 数据类型中存储的是可变长字符串,最大长度限制是2GB. 2.对于超出一定长度的文本,基本只能用LONG类型来存储,数据字典中很多对象的定义就是用LONG来存储的.1.LONG 数据类型中 ...

  8. Mysql 性能优化20个原则(4)

    16. 垂直分割 “垂直分割”是一种把数据库中的表按列变成几张表的方法,这样可以降低表的复杂度和字段的数目,从而达到优化的目的.(以前,在银行做过项目,见过一张表有100多个字段,很恐怖) 示例一:在 ...

  9. Shannon-Fano-Elias编码的C语言实现

    Shannon-Fano-Elias编码 一.理论分析 Shannon-Fano-Elias编码是利用累积分布函数来分配码字. 不失一般性,假定取X={1,2,-m}.如果对于全部的x,有p(x)&g ...

  10. [转】 nginx rewrite规则

    http://www.cnblogs.com/cgli/archive/2011/05/16/2047920.html 最近在VPS上尝试配置安装一个网站,VPS安装了LNMP(Linux+Nginx ...