简体   繁体   English

F#MailboxProcessor限制并行性

[英]F# MailboxProcessor limit parallelism

I'm new to F# and trying to experiment with the MailboxProcessor to ensure that state changes are done in isolation. 我是F#的新手并尝试使用MailboxProcessor来确保状态更改是孤立完成的。

In short, I am posting actions (immutable objects describing state chanage) to the MailboxProcessor, in the recursive function I read the message and generate a new state (ie add an item to a collection in the example below) and send that state to the next recursion. 简而言之,我将动作(描述状态通道的不可变对象)发布到MailboxProcessor,在递归函数中我读取消息并生成新状态(即在下面的示例中将项添加到集合中)并将该状态发送到下一次递归。

open System

type AppliationState =
    {
        Store : string list
    }
    static member Default = 
        {
            Store = List.empty
        }
    member this.HandleAction (action:obj) =
        match action with
        | :? string as a -> { this with Store = a :: this.Store }
        | _ -> this

type Agent<'T> = MailboxProcessor<'T>     

[<AbstractClass; Sealed>]
type AppHolder private () =
    static member private Processor = Agent.Start(fun inbox ->
        let rec loop (s : AppliationState) =
            async {
                let! action = inbox.Receive()
                let s' = s.HandleAction action
                Console.WriteLine("{s: " + s.Store.Length.ToString() + " s': " + s'.Store.Length.ToString())
                return! loop s'
                }
        loop AppliationState.Default)

    static member HandleAction (action:obj) =
        AppHolder.Processor.Post action

[<EntryPoint>]
let main argv =
    AppHolder.HandleAction "a"
    AppHolder.HandleAction "b"
    AppHolder.HandleAction "c"
    AppHolder.HandleAction "d"

    Console.ReadLine()
    0 // return an integer exit code

Expected output is: 预期产出是:

s: 0 s': 1
s: 1 s': 2
s: 2 s': 3
s: 3 s': 4  

What I get is: 我得到的是:

s: 0 s': 1
s: 0 s': 1
s: 0 s': 1
s: 0 s': 1

Reading the documentation for the MailboxProcessor and googling about it my conclusion is that it is a Queue of messages, processed by a 'single-thread', instead it looks like they are all processed in parallel. 阅读MailboxProcessor的文档并在上面搜索我的结论是它是一个由“单线程”处理的消息队列,而不是看起来它们都是并行处理的。

Am I totally off the field here? 我完全不在这里吗?

The issue is that you think AppHolder.Processor is going to be the same object each time, but it's actually a different MailboxProcessor each time. 问题是您认为AppHolder.Processor都是同一个对象,但实际上每次都是一个不同的MailboxProcessor。 I changed your AppHolder code to be the following: 我将您的AppHolder代码更改为以下内容:

[<AbstractClass; Sealed>]
type AppHolder private () =
    static member private Processor =
        printfn "Starting..."
        Agent.Start(fun inbox ->
        let rec loop (s : AppliationState) =
            async {
                let! action = inbox.Receive()
                let s' = s.HandleAction action
                printfn "{s: %A s': %A}" s s'
                return! loop s'
                }
        loop AppliationState.Default)

    static member HandleAction (action:obj) =
        AppHolder.Processor.Post action

The only changes I made was to simplify that Console.WriteLine call to use printfn and %A to get more debugging detail, and to add a single printfn "Starting..." call that will be executed immediately before the MailboxProcessor is built and started. 我做的唯一更改是简化Console.WriteLine调用以使用printfn%A获取更多调试细节,并添加一个printfn "Starting..."调用,该调用将在构建和启动MailboxProcessor之前立即执行。 And the output I got was: 我得到的输出是:

Starting...
Starting...
Starting...
Starting...
{s: {Store = [];} s': {Store = ["b"];}}
{s: {Store = [];} s': {Store = ["d"];}}
{s: {Store = [];} s': {Store = ["c"];}}
{s: {Store = [];} s': {Store = ["a"];}}

Notice that the printfn "Starting..." line has been executed four times. 请注意, printfn "Starting..."行已执行四次。

This catches a lot of F# newbies: the member keyword defines a property , not a field. 这捕获了很多F#新手: member关键字定义了一个属性 ,而不是一个字段。 Each time you evaluate the property, the body of that property is evaluated afresh. 每次评估属性时,都会重新评估该属性的主体。 So each time you access AppHolder.Processor , you get a new MailboxProcessor. 因此,每次访问AppHolder.Processor ,都会获得一个新的MailboxProcessor。 See https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/members/properties for more details. 有关详细信息,请参阅https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/members/properties

What you probably wanted was the following: 您可能想要的是以下内容:

[<AbstractClass; Sealed>]
type AppHolder private () =
    static let processor =
        printfn "Starting..."
        Agent.Start(fun inbox ->
            // ...
        )

    static member HandleAction (action:obj) =
        processor.Post action

I think the issue must be in your implementation of HandleAction. 我认为问题必须在你的HandleAction实现中。 I implemented the following, and it produces the expected output. 我实现了以下内容,它产生了预期的输出。

open System

type ApplicationState =
    {
        Items: int list
    }
    static member Default = {Items = []}
    member this.HandleAction x = {this with Items = x::this.Items}

type Message = Add of int

let Processor = MailboxProcessor<Message>.Start(fun inbox ->
    let rec loop (s : ApplicationState) =
        async {
            let! (Add action) = inbox.Receive()
            let s' = s.HandleAction action
            Console.WriteLine("s: " + s.Items.Length.ToString() + " s': " + s'.Items.Length.ToString())
            return! loop s'
        }
    loop ApplicationState.Default)

Processor.Post (Add 1)
Processor.Post (Add 2)
Processor.Post (Add 3)
Processor.Post (Add 4)


// OUTPUT
// s: 0 s': 1
// s: 1 s': 2
// s: 2 s': 3
// s: 3 s': 4

EDIT 编辑

After seeing the updated code sample, I believe the correct F# solution would just be to switch the AppHolder type from being a class to a module. 在看到更新的代码示例后,我相信正确的F#解决方案只是将AppHolder类型从一个类切换到一个模块。 The updated code would like this: 更新后的代码是这样的:

open System

type AppliationState =
    {
        Store : string list
    }
    static member Default = 
        {
            Store = List.empty
        }
    member this.HandleAction (action:obj) =
        match action with
        | :? string as a -> { this with Store = a :: this.Store }
        | _ -> this

type Agent<'T> = MailboxProcessor<'T>     

module AppHolder =
    let private processor = Agent.Start(fun inbox ->
        let rec loop (s : AppliationState) =
            async {
                let! action = inbox.Receive()
                let s' = s.HandleAction action
                Console.WriteLine("{s: " + s.Store.Length.ToString() + " s': " + s'.Store.Length.ToString())
                return! loop s'
            }
        loop AppliationState.Default)

    let handleAction (action:obj) =
        processor.Post action


AppHolder.handleAction "a"
AppHolder.handleAction "b"
AppHolder.handleAction "c"
AppHolder.handleAction "d"

This outputs the same result as before: 这输出与以前相同的结果:

{s: 0 s': 1
{s: 1 s': 2
{s: 2 s': 3
{s: 3 s': 4

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM