スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

F#入門中級編第20回(Asynchronous Workflows(6) MailboxProcessor(1))

F#では非同期的にメッセージをやりとりするオブジェクトを利用することができますので、これを紹介していきたいと思います。
このようなオブジェクトを作り出すクラスはMailboxProcessor<'Msg> クラスであり、そのインスタンスをagent(エージェント)と呼びます。
agentはメッセージキューをカプセル化しており、「そこからメッセージを読みだして、何らかの処理をする」という非同期処理を実行することができます。まずは一つインスタンスを作成してみます。
まずはコンストラクタですがこれは引数として、(MailboxProcessor<'Msg> -> Async<unit>) 型の関数をとります。
つまり、「インスタンス化された時の自分自身が引数となり実行する非同期処理を返す関数」がコンストラクタの引数となります。
まずは例を一つ
これは、せっかく持っているメッセージキューをまったく調べもせず、一つの非同期処理を実行するだけのさぼりで、すぐ仕事をやめてしまうagentです。
 
> let foolAgent00 = new MailboxProcessor<int> (fun inbox ->
                  async{do printfn"私にはメッセージキューを調べる気がありません"});;
 
 
val foolAgent00 : MailboxProcessor<int>
 
これは、メッセージキューをまったく使いませんので、型をintにしたのには特に意味がありません。
働かせてみます。これにはStartメソッドを使います。
 
> foolAgent00.Start();;
val it : unit = ()
>
私にはメッセージキューを調べる気がありません
 
上の例では、インスタンスの生成と仕事を始めさせるのを2つのStepで行いましたが、staticメソッドのStartを用いるとワンステップで行うことができます。
 
> let foolAgent01 = MailboxProcessor<int>.Start(fun inbox ->
                    async{do printfn"私もメッセージキューを調べる気がありません"});;
 
val foolAgent01 : MailboxProcessor<int>
 
>
私もメッセージキューを調べる気がありません
 
それでは、少しはやる気を出してもらって、メッセージキューを調べてもらいましょう。
それにはRecieveメソッドを使います。
コンストラクタの引数のinboxはインスタンス化されたときの、agentを表しますので、inbox.Recieve()とすれば、非同期処理を実行中のagentにRecieveメソッドを実行させることができます。
次のようにします。
 
> let foolAgent02 = MailboxProcessor<int>.Start(fun inbox ->
                    async{do printfn"少しやる気が出てきたのでメッセージキューを調べてみます,空なら待ちます"
                          let! msg = inbox.Receive()  //#1
                          do printfn "メッセージキューには%dが入っていました" msg
                          do printfn "疲れたので失礼させていただきます"})
;;
 
val foolAgent02 : MailboxProcessor<int>
 
>
少しやる気が出てきたのでメッセージキューを調べてみます,空なら待ちます
 
#1の部分に着目してください。
Recieveメソッドは、非同期にメッセージキューを調べ、あればそれを返し、なければメッセージがあるまでそれを待つという作業をします。これ自体非同期処理なので、letに!がつくという訳です。
上を実行した段階ではメッセージキューは空ですので、今もfoolAgent02はメッセージを待っている途中です。それではメッセージを送ってみます。これにはPostメソッドを使います。(Postメソッドも非同期処理です。)
 
> foolAgent02.Post(7);;
val it : unit = ()
>
メッセージキューには7が入っていました
疲れたので失礼させていただきます
 
これでやっと、メッセージキューを調べてくれるようになりましたが、このままでは、一回メッセージキューを調べたら、それで作業を終わってしまうagentでしかありません。ずっと待機していてメッセージがあれば働くという、働き者のagentを次は作りたいと思います。
これには、仕事をどんどん与え続ければよいので、仕事内容をループにします。つまり再帰を用いることになります。
 

 
> let workaholicAgent00 = MailboxProcessor<int>.Start(fun inbox ->
                        let rec loop () =
                            async{do printfn"やる気満々でメッセージキューを調べてみます"
                                  let! msg = inbox.Receive()
                                  do printfn "メッセージキューには%dが入っていました" msg
                                  do printfn "次の仕事に向かいます"
                                  return! loop () //#1 
                            }
                        loop ()  )
;;
 
val workaholicAgent00 : MailboxProcessor<int>
 
>
やる気満々でメッセージキューを調べてみます
 
#1の部分に着目ください。一回目の仕事が終わり#1まで、進むと再帰呼び出しで、新しい非同期処理が待っているという仕組みです。loop ()自体が非同期処理ですのでreturnに!が付きます。
 
> workaholicAgent00.Post(7);;
val it : unit = ()
>
メッセージキューには7が入っていました
次の仕事に向かいます
やる気満々でメッセージキューを調べてみます
workaholicAgent00.Post(9);;
メッセージキューには9が入っていました
次の仕事に向かいます
やる気満々でメッセージキューを調べてみます
val it : unit = ()
> workaholicAgent00.Post(1);;
メッセージキューには1が入っていました
次の仕事に向かいます
やる気満々でメッセージキューを調べてみます
val it : unit = ()

 
さて、ここまでのagentは記憶保持力ゼロなので、何か覚えておいてもらうことにします。さて、どこにその情報を保持するかですが、これは内部の再帰的に呼び出される関数の引数として、覚えておいてもらうことにします。
 
例(処理した整数値を覚えておく)
 
> let workaholicAgent01 = MailboxProcessor<int>.Start(fun inbox ->
                        let rec loop lst =
                            async{do printfn "いままでに処理したのは%Aです" lst
                                  let! msg = inbox.Receive()
                                  do printfn "メッセージキューには%dが入っていました" msg
                                  do printfn "次の仕事に向かいます"
                                  return! loop (msg::lst) 
                            }
                        loop []  );;
 
val workaholicAgent01 : MailboxProcessor<int>
 
> いままでに処理したのは[]です
 
> workaholicAgent01.Post(1);;
val it : unit = ()
>
メッセージキューには1が入っていました
次の仕事に向かいます
いままでに処理したのは[1]です
 
workaholicAgent01.Post(9);;
 
メッセージキューには9が入っていました
次の仕事に向かいます
val it : unit = ()
いままでに処理したのは>
[9; 1]です
 
さてここまでではagentはメッセージを待ち続けますので、停止することがありません。次にagentに停止の合図を送れるようにします。
それにはメッセージをdiscriminated unionにして、停止の合図も送ることができるようにします。
例えば次のようになります。
 
> type msg = 
    |Work of int
    |Stop
 
let canStopAgent00 = MailboxProcessor.Start(fun inbox ->
                        let rec loop lst =
                            async{do printfn"いままでキューに入っていたのは%Aです" lst
                                  let! msg = inbox.Receive()
                                  match msg with
                                  | Work(i) ->
                                    do printfn "メッセージキューには%dが入っていました" i
                                    do printfn "次の仕事に向かいます"
                                    return! loop (msg::lst) //#1
                                  | Stop ->
                                    do printfn "終了の合図を受け取ったので仕事をやめます"
                                    return ()
                            }
                        loop []  );;
 
type msg =
  | Work of int
  | Stop
val canStopAgent00 : MailboxProcessor<msg>
 
>
いままでキューに入っていたのは[]です
 
canStopAgent00.Post(Work(1));; // 入力部分
val it : unit = ()
>
メッセージキューには1が入っていました
次の仕事に向かいます
いままでキューに入っていたのは[Work 1]です
 
canStopAgent00.Post(Stop);; // 入力部分
val it : unit = ()
終了の合図を受け取ったので仕事をやめます
 

スポンサーサイト

テーマ : プログラミング
ジャンル : コンピュータ

コメントの投稿

非公開コメント

プロフィール

T GYOUTEN

Author:T GYOUTEN
F#と英単語とフリーソフトと読書に興味があります。
ホームページでフリーソフトも公開しています。どぞ御贔屓に。

最新記事
最新コメント
最新トラックバック
月別アーカイブ
カテゴリ
フリーエリア
フリーエリア
blogram投票ボタン
検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム

この人とブロともになる

QRコード
QRコード
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。