在 C#ReceiveActor
中,我可以将状态作为类中的私有字段。我应该如何使用 F# API 以惯用的方式执行此操作?
这是一个好主意吗?有什么选择吗?
let handleMessage (mailbox: Actor<'a>) msg =
let mutable i = 1
match msg with
| Some x -> i <- i + x
| None -> ()
您提出的方式完全适合作为在参与者中存储状态的一种方式。任何时候只处理 1 条消息的并发约束意味着不可能由于共享内存位置的争用而进入无效状态。
但是,这不是最惯用的选择。Akka.Net 提供了一个 F# API 以与 F# MailboxProcessors 类似的方式与参与者一起工作。在这种情况下,您将您的 actor 定义为一个尾递归函数,该函数以一些新状态调用自身。这是一个例子
spawn system "hello" <|
fun mailbox ->
let rec loop state =
actor {
let! msg = mailbox.Receive ()
printfn "Received %A. Now received %s messages" msg state
return! loop (state + 1) //Increment a counter for the number of times the actor has received a message
}
loop 0
有关 Akka.Net F# API 的完整文档,请参阅http://getakka.net/wiki/FSharp%20API
有两种解决方案,它们都使用显式递归循环定义,这是 Akka F#actors 的主要概念。
首先,您可以在循环定义之前定义变量,这些变量应该只在actor的范围内可见(在下面的示例中,我已将i
定义更改为引用单元格,因为闭包无法捕获可变变量):
let actorRef =
spawn system "my-actor" <| fun mailbox ->
let i = ref 1
let rec loop () =
actor {
let! msg = mailbox.Receive()
match msg with
| Some x -> i := !i + x
| None -> ()
return! loop()
}
loop()
但是,更建议的解决方案是在消息处理期间保持您的状态不可变,并且仅在传递下一个循环调用时更改它,就像这样:
let actorRef =
spawn system "my-actor" <| fun mailbox ->
let rec loop i =
actor {
let! msg = mailbox.Receive()
match msg with
| Some x -> return! loop (i + x)
| None -> return! loop i
}
loop 1 // invoke first call with initial state