views:

250

answers:

2

I have been slowly examining all of the features that F# brings to the table. One that has particularly piqued my interest is the MailboxProcessor.

  1. The equivalent of this in C# would most likely use locks. Can we consider the MailboxProcessor as a replacement for locks?
  2. In the following example, am I doing anything particularly naive or can you see anything that might be improved?


module Tcp =
    open System
    open System.Collections.Generic
    open System.Net
    open System.Net.Sockets
    open System.Threading    

    type SocketAsyncMessage =
        | Get of AsyncReplyChannel<SocketAsyncEventArgs>
        | Put of SocketAsyncEventArgs
        | Dispose of AsyncReplyChannel<MailboxProcessor<SocketAsyncMessage>>

    type SocketAsyncEventArgsPool(size:int) =             
        let agent = 
            lazy(MailboxProcessor.Start(
                    (fun inbox ->
                        let references = lazy(new List<SocketAsyncEventArgs>(size))       
                        let idleReferences = lazy(new Queue<SocketAsyncEventArgs>(size))                    
                        let rec loop () = 
                            async {
                                let! message = inbox.Receive()
                                match message with
                                | Get channel -> 
                                    if idleReferences.Value.Count > 0 then
                                        channel.Reply(idleReferences.Value.Dequeue())
                                    else    
                                        let args = new SocketAsyncEventArgs()
                                        references.Value.Add args
                                        channel.Reply args  
                                    return! loop()
                                | Put args ->
                                    if args = null then
                                        nullArg "args" 
                                    elif references.Value.Count < size then
                                        idleReferences.Value.Enqueue args
                                    else                                       
                                        if not(references.Value.Remove args) then
                                            invalidOp "Reference not found."                                        
                                        args.Dispose() 
                                    return! loop()
                                | Dispose channel ->
                                    if references.IsValueCreated then
                                        references.Value 
                                        |> Seq.iter(fun args -> args.Dispose())
                                    channel.Reply inbox 
                            }
                        loop())))

        /// Returns a SocketAsyncEventArgs instance from the pool.         
        member this.Get () =
            agent.Value.PostAndReply(fun channel -> Get channel)            
        /// Returns the SocketAsyncEventArgs instance to the pool. 
        member this.Put args =
            agent.Value.Post(Put args)
        /// Releases all resources used by the SocketAsyncEventArgsPool.
        member this.Dispose () =
            (this:>IDisposable).Dispose()  

        interface IDisposable with
             member this.Dispose() =
                if agent.IsValueCreated then
                    (agent.Value.PostAndReply(fun channel -> Dispose channel):>IDisposable).Dispose()
+5  A: 

Mailboxes (and similar constructs) are used in programming models that don't use locks, as they're inherently built around asynchronous processing. (Lack of shared mutable state is another requirement of this model).

The Actor model can be thought of as a series of single-threaded mini-applications that communicate by sending and receiving data from each other. Each mini-application will only be run by a single thread at a time. This, combined with the lack of shared state, renders locks unnecessary.

Procedural models (and most OO code is, at its heart, procedural), use thread-level concurrency, and synchronous calls to other objects. The Actor model flips this around - calls (messages) between objects are asynchronous, but each object is completely synchronous.

I don't know enough F# to really analyze your code, frankly. It does look like you're trying to stick a synchronous-looking shell around your mailbox, and I wonder if that's really the best thing to do (vs. embracing the mailbox model fully). In your implementation, it does appear that you're using it as a replacement for a lock.

kyoryu
You know you are absolutely right. There is no reason for me to wait for the args object when I can pass a function. The functions that would need the args object are asynchronous anyway.
ChaosPandion
A: 

To first part of your question:

The MailboxProcessor class is a message queue running on its own thread. You may send an message to the MailboxProcessor from any thread as asynchronously as synchronously.

Such model allows to communicate between threads through message passing instead of using locks/mutexes/ipc mechanics.

ssp
Typicall, in this model, it's better stated as allowing communication between Actors, not threads. Actors can be served by arbitrary threads, but will typically guarantee only being served by a single thread at a time. The lack of 1:1 relationship between actors and threads allows the system to scale up to much higher 'process' count than if a 1:1 mapping existed.
kyoryu