Search code examples
asynchronousf#mailboxprocessor

Unable to keep state with mailbox processor (F#)


I am attempting to create a list of strings which gets elements gradually inserted into asynchronously with the help of a mailbox processor. However I am not getting the desired output.

I have pretty much followed the code from https://fsharpforfunandprofit.com/posts/concurrency-actor-model/ however it does not seem to work as intended in my case. The code I have is as follows:

type TransactionQueue ={

queue : string list

} with

static member UpdateState (msg : string) (tq : TransactionQueue) =
    {tq with queue = (msg :: tq.queue)}

static member Agent = MailboxProcessor.Start(fun inbox ->
                            let rec msgLoop (t : TransactionQueue) =
                                async{
                                   let! msg = inbox.Receive()
                                   let newT = TransactionQueue.UpdateState msg t
                                   printfn "%A" newT
                                   return! msgLoop newT
                                }
                            msgLoop {queue = []}
                        )

static member Add i = TransactionQueue.Agent.Post i



[<EntryPoint>]
let main argv =

// test in isolation
printfn "welcome to test"
let rec loop () =
    let str = Console.ReadLine()
    TransactionQueue.Add str
    loop ()

loop ()



0 

The result i keep getting is a list of the latest input only, the state is not kept. So if I enter "a" then "b" then "c" the queue will only have the value "c" instead of "a";"b";"c"

Any help or pointers would be most appreciated!


Solution

  • Just like C# Properties, your Agent is really a Property and thus behaves like a method with void parameter. That’s why you will get a new agent everytime Agent property is accessed.

    In idiomatic F# there are two styles when implementing agents. If you don’t need to have many agent instances, just write a module and encapsule the agent-related stuff inside. Otherwise, OOP style should be used.

    Code for style #1

    module TransactionQueue =
        type private Queue = Queue of string list
        let private empty = Queue []
        let private update item (Queue items) = Queue (item :: items)
        let private agent = MailboxProcessor.Start <| fun inbox ->
            let rec msgLoop queue = async {
                let! msg = inbox.Receive ()
                return! queue |> update msg |> msgLoop
            }
            msgLoop empty
        let add item = agent.Post item
    
    [<EntryPoint>]
    let main argv =
        // test in isolation
        printfn "welcome to test"
        let rec loop () =
            let str = Console.ReadLine()
            TransactionQueue.add str
            loop ()
        loop ()
    

    Code for style #2

    type Queue = Queue of string list with
        static member Empty = Queue []
        static member Update item (Queue items) =
            Queue (item :: items)
    
    type Agent () =
        let agent = MailboxProcessor.Start <| fun inbox ->
            let rec msgLoop queue = async {
                let! msg = inbox.Receive ()
                return! queue |> Queue.Update msg |> msgLoop
            }
            msgLoop Queue.Empty
        member this.Add item = agent.Post item
    
    [<EntryPoint>]
    let main argv =
        // test in isolation
        printfn "welcome to test"
        let agent = new Agent ()
        let rec loop () =
            let str = Console.ReadLine()
            agent.Add str
            loop ()
        loop ()
    

    Notice the use of Single-case union types for the Queue type.