다음을 통해 공유


Control.MailboxProcessor<'Msg> 클래스(F#)

업데이트: 2010년 8월

비동기 계산을 실행하는 메시지 처리 에이전트입니다.

네임스페이스/모듈 경로: Microsoft.FSharp.Control

어셈블리: FSharp.Core(FSharp.Core.dll)

[<Sealed>]
[<AutoSerializable(false)>]
type MailboxProcessor<'Msg> =
 class
  interface IDisposable
  new MailboxProcessor : (MailboxProcessor<'Msg> -> Async<unit>) * ?CancellationToken -> MailboxProcessor<'Msg>
  member this.Post : 'Msg -> unit
  member this.PostAndAsyncReply : (AsyncReplyChannel<'Reply> -> 'Msg) * int option -> Async<'Reply>
  member this.PostAndReply : (AsyncReplyChannel<'Reply> -> 'Msg) * int option -> 'Reply
  member this.PostAndTryAsyncReply : (AsyncReplyChannel<'Reply> -> 'Msg) * ?int -> Async<'Reply option>
  member this.Receive : ?int -> Async<'Msg>
  member this.Scan : ('Msg -> Async<'T> option) * ?int -> Async<'T>
  member this.Start : unit -> unit
  static member Start : (MailboxProcessor<'Msg> -> Async<unit>) * ?CancellationToken -> MailboxProcessor<'Msg>
  member this.TryPostAndReply : (AsyncReplyChannel<'Reply> -> 'Msg) * ?int -> 'Reply option
  member this.TryReceive : ?int -> Async<'Msg option>
  member this.TryScan : ('Msg -> Async<'T> option) * ?int -> Async<'T option>
  member this.add_Error : Handler<Exception> -> unit
  member this.CurrentQueueLength :  int
  member this.DefaultTimeout :  int with get, set
  member this.Error :  IEvent<Exception>
  member this.remove_Error : Handler<Exception> -> unit
 end

설명

에이전트는 여러 개의 작성기와 하나의 판독기 에이전트를 지원하는 메시지 큐를 캡슐화합니다. 작성기는 Post 메서드와 해당 변형을 사용하여 에이전트에 메시지를 보냅니다. 에이전트는 Receive 또는 TryReceive 메서드를 사용하여 메시지를 기다리거나 Scan 또는 TryScan 메서드를 사용하여 사용 가능한 모든 메시지를 검색할 수 있습니다.

.NET 어셈블리에서 이 형식의 이름은 FSharpMailboxProcessor입니다. F# 이외의 .NET 언어에서 형식에 액세스하거나 리플렉션을 통해 형식에 액세스하는 경우 이 이름을 사용합니다.

생성자

멤버

설명

new

에이전트를 만듭니다. body 함수는 에이전트에 의해 실행되는 비동기 계산을 생성하는 데 사용됩니다. 이 함수는 Start를 호출하기 전까지는 실행되지 않습니다.

인스턴스 멤버

멤버

설명

add_Error

에이전트 실행 결과 예외가 발생할 때 발생합니다.

CurrentQueueLength

에이전트의 메시지 큐에 있는 처리되지 않은 메시지 수를 반환합니다.

DefaultTimeout

지정된 시간 내에 메시지를 받지 못할 경우 시간 제한 예외를 발생시킵니다. 기본적으로는 시간 제한이 사용되지 않습니다.

Error

에이전트 실행 결과 예외가 발생할 때 발생합니다.

Post

메시지를 MailboxProcessor의 메시지 큐에 비동기 방식으로 게시합니다.

PostAndAsyncReply

메시지를 에이전트에 게시하고 비동기적으로 채널에서 회신을 기다립니다.

PostAndReply

메시지를 에이전트에 게시하고 동기적으로 채널에서 회신을 기다립니다.

PostAndTryAsyncReply

AsyncPostAndReply와 비슷하지만 시간 제한 기간 동안 회신이 없으면 None을 반환합니다.

Receive

메시지를 기다리고 도착 순서대로 첫 번째 메시지를 사용합니다.

remove_Error

에이전트 실행 결과 예외가 발생할 때 발생합니다.

Scan

scanner에서 Some 값을 반환할 때까지 도착 순서대로 메시지를 탐색하여 메시지를 검사합니다. 다른 메시지는 큐에 남아 있습니다.

시작

에이전트를 시작합니다.

TryPostAndReply

PostAndReply와 비슷하지만 시간 제한 기간 동안 회신이 없으면 None을 반환합니다.

TryReceive

메시지를 기다리고 도착 순서대로 첫 번째 메시지를 사용합니다.

TryScan

scanner에서 Some 값을 반환할 때까지 도착 순서대로 메시지를 탐색하여 메시지를 검사합니다. 다른 메시지는 큐에 남아 있습니다.

정적 멤버

멤버

설명

시작

에이전트를 만들고 시작합니다. body 함수는 에이전트에 의해 실행되는 비동기 계산을 생성하는 데 사용됩니다.

예제

다음 예제에서는 MailboxProcessor 클래스의 기본 사용 방법을 보여 줍니다.

open System
open Microsoft.FSharp.Control

type Message(id, contents) =
    static let mutable count = 0
    member this.ID = id
    member this.Contents = contents
    static member CreateMessage(contents) =
        count <- count + 1
        Message(count, contents)

let mailbox = new MailboxProcessor<Message>(fun inbox ->
    let rec loop count =
        async { printfn "Message count = %d. Waiting for next message." count
                let! msg = inbox.Receive()
                printfn "Message received. ID: %d Contents: %s" msg.ID msg.Contents
                return! loop( count + 1) }
    loop 0)

mailbox.Start()

mailbox.Post(Message.CreateMessage("ABC"))
mailbox.Post(Message.CreateMessage("XYZ"))


Console.WriteLine("Press any key...")
Console.ReadLine() |> ignore

샘플 출력

                    

다음 예제에서는 MailboxProcessor를 사용하여 다양한 형식의 메시지를 수락하고 적절한 회신을 반환하는 단순 에이전트를 생성하는 방법을 보여줍니다. 이 서버 에이전트는 자산에 대한 입찰가와 매도 가격을 설정하는 증권 거래소에서 에이전트를 매입 및 매도하는 투자 전문가를 나타냅니다. 클라이언트는 가격을 쿼리하거나 주식을 사고 팔 수 있습니다.

open System

type AssetCode = string

type Asset(code, bid, ask, initialQuantity) =
    let mutable quantity = initialQuantity
    member this.AssetCode = code
    member this.Bid = bid
    member this.Ask = ask
    member this.Quantity with get() = quantity and set(value) = quantity <- value


type OrderType =
    | Buy of AssetCode * int
    | Sell of AssetCode * int

type Message =
    | Query of AssetCode * AsyncReplyChannel<Reply>
    | Order of OrderType * AsyncReplyChannel<Reply>
and Reply =
    | Failure of string
    | Info of Asset
    | Notify of OrderType

let assets = [| new Asset("AAA", 10.0, 10.05, 1000000);
                new Asset("BBB", 20.0, 20.10, 1000000);
                new Asset("CCC", 30.0, 30.15, 1000000) |]

let codeAssetMap = assets
                   |> Array.map (fun asset -> (asset.AssetCode, asset))
                   |> Map.ofArray

let mutable totalCash = 00.00
let minCash = -1000000000.0
let maxTransaction = 1000000.0

let marketMaker = new MailboxProcessor<Message>(fun inbox ->
    let rec Loop() =
        async {
            let! message = inbox.Receive()
            match message with
            | Query(assetCode, replyChannel) ->
                match (Map.tryFind assetCode codeAssetMap) with
                | Some asset ->
                    printfn "Replying with Info for %s" (asset.AssetCode)
                    replyChannel.Reply(Info(asset))
                | None -> replyChannel.Reply(Failure("Asset code not found."))
            | Order(order, replyChannel) ->
                match order with
                | Buy(assetCode, quantity) ->
                    match (Map.tryFind assetCode codeAssetMap) with
                    | Some asset ->
                        if (quantity < asset.Quantity) then
                            asset.Quantity <- asset.Quantity - quantity
                            totalCash <- totalCash + float quantity * asset.Ask
                            printfn "Replying with Notification:\nBought %d units of %s at price $%f. Total purchase $%f."
                                    quantity asset.AssetCode asset.Ask (asset.Ask * float quantity)
                            printfn "Marketmaker balance: $%10.2f" totalCash
                            replyChannel.Reply(Notify(Buy(asset.AssetCode, quantity)))
                        else
                            printfn "Insufficient shares to fulfill order for %d units of %s."
                                    quantity asset.AssetCode
                            replyChannel.Reply(Failure("Insufficient shares to fulfill order."))
                    | None -> replyChannel.Reply(Failure("Asset code not found."))
                | Sell(assetCode, quantity) ->
                    match (Map.tryFind assetCode codeAssetMap) with
                    | Some asset ->
                        if (float quantity * asset.Bid <= maxTransaction && totalCash - float quantity * asset.Bid > minCash) then
                            asset.Quantity <- asset.Quantity + quantity
                            totalCash <- totalCash - float quantity * asset.Bid
                            printfn "Replying with Notification:\nSold %d units of %s at price $%f. Total sale $%f."
                                    quantity asset.AssetCode asset.Bid (asset.Bid * float quantity)
                            printfn "Marketmaker balance: $%10.2f" totalCash
                            replyChannel.Reply(Notify(Sell(asset.AssetCode, quantity)))
                        else
                            printfn "Insufficient cash to fulfill order for %d units of %s."
                                    quantity asset.AssetCode
                            replyChannel.Reply(Failure("Insufficient cash to cover order."))
                    | None -> replyChannel.Reply(Failure("Asset code not found."))
            do! Loop()
        }
    Loop())

marketMaker.Start()

// Query price.
let reply1 = marketMaker.PostAndReply(fun replyChannel -> 
    printfn "Posting message for AAA"
    Query("AAA", replyChannel))

// Test Buy Order.
let reply2 = marketMaker.PostAndReply(fun replyChannel -> 
    printfn "Posting message for BBB"
    Order(Buy("BBB", 100), replyChannel))

// Test Sell Order.
let reply3 = marketMaker.PostAndReply(fun replyChannel -> 
    printfn "Posting message for CCC"
    Order(Sell("CCC", 100), replyChannel))

// Test incorrect code.
let reply4 = marketMaker.PostAndReply(fun replyChannel -> 
    printfn "Posting message for WrongCode"
    Order(Buy("WrongCode", 100), replyChannel))

// Test too large a number of shares.

let reply5 = marketMaker.PostAndReply(fun replyChannel ->
    printfn "Posting message with large number of shares of AAA."
    Order(Buy("AAA", 1000000000), replyChannel))

// Too large an amount of money for one transaction.

let reply6 = marketMaker.PostAndReply(fun replyChannel ->
    printfn "Posting message with too large of a monetary amount."
    Order(Sell("AAA", 100000000), replyChannel))

let random = new Random()
let nextTransaction() =
    let buyOrSell = random.Next(2)
    let asset = assets.[random.Next(3)]
    let quantity = Array.init 3 (fun _ -> random.Next(1000)) |> Array.sum
    match buyOrSell with
    | n when n % 2 = 0 -> Buy(asset.AssetCode, quantity)
    | _ -> Sell(asset.AssetCode, quantity)

let simulateOne() =
   async {
       let! reply = marketMaker.PostAndAsyncReply(fun replyChannel ->
           let transaction = nextTransaction()
           match transaction with
           | Buy(assetCode, quantity) -> printfn "Posting BUY %s %d." assetCode quantity
           | Sell(assetCode, quantity) -> printfn "Posting SELL %s %d." assetCode quantity
           Order(transaction, replyChannel))
       printfn "%s" (reply.ToString())
    }

let simulate =
    async {
        while (true) do
            do! simulateOne()
            // Insert a delay so that you can see the results more easily.
            do! Async.Sleep(1000)
    }

Async.Start(simulate)

Console.WriteLine("Press any key...")
Console.ReadLine() |> ignore

샘플 출력

                                  

플랫폼

Windows 7, Windows Vista SP2, Windows XP SP3, Windows XP x64 SP2, Windows Server 2008 R2, Windows Server 2008 SP2, Windows Server 2003 SP2

버전 정보

F# 런타임

지원되는 버전: 2.0, 4.0

Silverlight

지원되는 버전: 3

참고 항목

참조

Microsoft.FSharp.Control 네임스페이스(F#)

변경 기록

날짜

변경 내용

이유

2010년 8월

코드 예제를 추가했습니다.

향상된 기능 관련 정보