开发者

Is the MailboxProcessor type a replacement for locks?

开发者 https://www.devze.com 2022-12-19 17:21 出处:网络
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.

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开发者_StackOverflow中文版<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()


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.


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消