我注意到 Mailbox 类型是封装的,只能通过使用 MailboxProcessor 来使用。
这意味着要拥有一个可以向其发布消息的代理,我必须拥有一个单一类型的邮箱(或以奇特的方式使用现有的 MailboxProcessor)。
我是否应该理解为单个工作流程设置多个邮箱本质上会导致糟糕的设计?Ccr 显然为您提供了这种程度的自由。
编辑:正如丹尼尔指出的那样,如果一个人想发送多种消息类型,DUs 优雅地解决了这个问题——而且我过去也没有这样做过。
但问题是,这样做是不是一种代码味道?随着时间的推移,添加更多类型的发送给代理的消息会不会导致您承担太多责任?我有时认为始终将代理使用的消息类型封装在接口后面很重要,这样这些信息就不会公开。
我认为 F# 代理使用MailboxProcessor
和 CCR 实现了不同的编程模型,但我相信两者同样强大,尽管肯定存在可以通过一个或另一个更好地解决的问题,因此拥有另一个库用于F# 围绕邮箱构建。基于CCR的编程模型可能在COmega(这是一个老的MSR项目)等基于join calculus的各种语言中描述得更清楚。
例如,您可以比较使用 COmega 和 F# 代理的单一缓冲区的实现:
public class OnePlaceBuffer {
private async empty();
private async contains(string s);
public OnePlaceBuffer() { empty(); }
public void Put(string s) & empty() {
contains(s);
}
public string Get() & contains(string s) {
empty();
return s;
}
}
Run Code Online (Sandbox Code Playgroud)
在这个例子中,异步方法的行为就像邮箱(所以有他们四个:empty
,contains
,Put
和Get
)和尸体的行为那样会触发处理程序时,邮箱的组合中包含的值(即当你把到一个空的缓冲区或当你从一个完整的缓冲区中获取时)。在 F# 中,您可以使用和编写:MailboxProcessor
type Message<'T> =
| Put of 'T * AsyncReplyChannel<unit>
| Get of AsyncReplyChannel<'T>
MailboxProcessor.Start(fun agent ->
let rec empty = agent.Scan(function
| Put(v, repl) -> repl.Reply(); Some(full(v))
| _ -> None)
and full v = agent.Scan(function
| Get repl -> repl.Reply(v); Some(empty)
| _ -> None)
empty )
Run Code Online (Sandbox Code Playgroud)
这两种实现表达了相同的想法,但方式略有不同。在 F# 中,empty
和full
是代表代理不同状态的两个函数,发送给代理的消息代表代理状态的不同方面(待处理的工作)。在 COmega 实现中,程序的所有状态都由邮箱捕获。
我想将代理的状态与需要处理的即时消息分开可能会使考虑 F# 更容易MailboxProcessor
一些,但这只是没有理由的即时想法......
最后,在MailboxProcessor
F#中使用的实际应用程序中,您很可能会使用更多的它们,并且它们会以某种方式连接起来。例如,实现流水线是使用多个MailboxProcessor
实例的应用程序的一个很好的例子(当然,所有实例都有一些与之关联的简单运行的异步工作流)。有关示例,请参阅本文。