The following simple example shows how to use F# Mailbox to develop an Agent (or Actor) based application.
Each Agent is a state-machine.
Each Agent do actions based on receiving messages as changing state (state-machine) or replying to the caller.
The action can be done asynchronous, allowing the application to easy scale keeping performance.
You can test and debug the application using F# interactive by send/receive messages to the Agents interactive.
Example
The example uses 3 Agents.
The agent2 and agent3 are of type agentCalc. It receives messages as 'Two' and 'Three' together with an integer. These Agents multiply the receiving number by 2 or by 3 depending on the message.
The agent1 has 2 states: idle and running. It also keeps a totalizer.
It receives messages as: 'Start', 'Exit', 'Reset', 'GoIdle', 'Next', 'Show' and 'State'.
These messages may have different meaning depending of the state of agent1.
For example:
In 'idle':
- 'Start': change the state to 'running'
- 'Exit': agent stop execution
In 'running':
- 'GoIdle': change the state to 'idle'
- 'Reset': totalizer goes to 0
- 'Next': generates 2 random integers (0..1) and (0..9).
The first number is used to decide if the message to be sent will be 'Two' or 'Three'.
Then it send a message to agent2 and agent3 passing the second number.
The returning number from agent2 and agent3 is added to the totalizer.
The following script implements the agents:
open System type msg1 = | Start | Exit | Reset | GoIdle | Next | Show of AsyncReplyChannel| State of AsyncReplyChannel type msg2 = Two of int*AsyncReplyChannel | Three of int*AsyncReplyChannel let agentCalc ()= MailboxProcessor.Start(fun i -> let rec running = async { let! msg= i.Receive() match msg with | Two (n,replyChannel) -> do replyChannel.Reply(n*2) return! running | Three (n, replyChannel) -> do replyChannel.Reply(n*3) return! running } running ) let agent2= agentCalc () let agent3= agentCalc () let mutable total=0 let agent1= MailboxProcessor.Start(fun i -> let rec idle (total:int)= async { let! msg= i.Receive() match msg with | Reset -> let total=0 return! idle total | Exit -> return () | Start -> return! running total | Show (replyChannel)-> do replyChannel.Reply(total) return! idle total | State (replyChannel)-> do replyChannel.Reply("idle") return! idle total | _-> return! idle total } and running total= async{ let! msg= i.Receive() match msg with | Next -> let z=new Random() let n1=z.Next(1) let n2=z.Next(9) let total= if n1=0 then let r1=agent2.PostAndReply(fun replyChannel->Two(n2,replyChannel)) let r2=agent3.PostAndReply(fun replyChannel->Two(n2,replyChannel)) total+r1+r2 else let r1=agent2.PostAndReply(fun replyChannel->Three(n2,replyChannel)) let r2=agent3.PostAndReply(fun replyChannel->Three(n2,replyChannel)) total+r1+r2 return! running total | GoIdle -> return! idle total | Show (replyChannel) -> do replyChannel.Reply(total) return! running total | State (replyChannel)-> do replyChannel.Reply("running") return! running total | Reset -> let total=0 return! running total | _-> return! idle total } idle total)
The following script tests interactivelly the agents:
let r1 = agent1.PostAndReply(fun reply -> State(reply)) printfn "State %s" r1 |>ignore;; State idle let r2 = agent1.PostAndReply(fun reply -> Show(reply)) printfn "Total %d" r2 |>ignore;; Total 0 agent1.Post(Start) let r3 = agent1.PostAndReply(fun reply -> State(reply)) printfn "State %s" r3 |>ignore;; State running for i in 1..5 do agent1.Post(Next) let r4 = agent1.PostAndReply(fun reply -> Show(reply)) printfn "Total %d" r4 |>ignore;; Total 24 Total 48 Total 72 Total 96 Total 120 agent1.Post(Reset) let r5 = agent1.PostAndReply(fun reply -> Show(reply)) printfn "Total %d" r5 |>ignore;; Total 0 agent1.Post(GoIdle) let r6 = agent1.PostAndReply(fun reply -> State(reply)) printfn "State %s" r6 |>ignore;; State idle agent1.Post(Exit);;
No comments:
Post a Comment