スポンサーサイト

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

F#入門中級編第16回(Asynchronous Workflows(2))

今回のお題は「Asynchronous Workflows(2) 」です。
 
スレッドを走らして、その結果が必要となる時.NETではAPM(asynchoronous programing model)という形の非同期処理を利用するのが一般的です。
これは非同期処理をBeginOperationNameとEndOperationNameという二つのメソッドに分けて行います。
また非同期処理の情報は IAsyncResultオブジェクトに格納されます。この中のAsyncStateメンバーとしてユーザー定義の情報を含めることができます。
BeginOperationNameメソッドは呼び出されると処理を非同期的に開始しますが、同時にIAsyncResultオブジェクトを返します。
また非同期的処理の終了時には処理に付加しておいたコールバックデリゲイト(引数には IAsyncResultオブジェクトが与えられる)が実行されます。
また結果を取得するにはEndInvokeメソッドを使います。(これは必ず呼び出す必要あり)
 
「非同期処理に呼び出した処理がいつ終わったか」をとらえるにはいくつか方法があるのですが、そのうちのいくつかを紹介します。
 
まずは呼び出し側の方で,EndInvokeメソッドを使って呼び出しが終了するまでブロックして、結果を待ち合わせる方法です。
 
とりあえず例を挙げてみます。
 
まず、バックグラウンドでやりたい仕事を表す関数を一つ用意します。
 
let  myWhatToDoDlgt = (fun (s:string,n:int) ->
                            for i in 0 .. (n - 1) do
                                Thread.Sleep(1)
                                printfn "%d :%s" i s
                            10 * n)   
 
さらに、この関数をラップした、Func型のオブジェクトを用意します。
 
let myWhatToDoFunc  = new Func<_,_> (myWhatToDoDlgt)
 
WhatToDoFuncは与えた文字をn回表示し、引数*10を返すという関数をラップしたFunc型のオブジェクトになります。
 
このFunc型のオブジェクトのBeginInvokeメソッドを利用して非同期処理を開始しますが、この時でIAsyncResultオブジェクト(非同期処理の進行具合やユーザー定義情報が格納されます)が作成されます。BeginInvokeメソッドには次の引数を設定します。
BeginInvoke(WhatToDoFuncへの引数,
            WhatToDoFuncの実行が終わったときに呼び出されるコールバック関数(IAsynResult->unit型)をラップした AsyncCallback デリゲイト,
            開始前にIAsyncResultオブジェクトへ付加するユーザー定義情報(これはIAsyncResultオブジェクトのStateメンバーとして保存されます))
 
コールバック関数はIAsyncResultオブジェクト(WhatToDoFuncが終わった時点では、進行具合を表すIsCompletedプロパティなどが更新されています)を引数としunit型を返す必要があります。WhatToDoFuncの実行が終わった時点で、更新されたIAsyncResultオブジェクトが渡されますので、これを念頭において内容を定義します。例えば次のように定義してみます。
 
let myCallback =   new  AsyncCallback
                              (fun (iarInDeleg:IAsyncResult) ->
                                 printfn  "delegate end. Start Time was %A"  ((iarInDeleg.AsyncState).ToString())
                                 printfn "iar.IsCompleted is %A at Position 1" iarInDeleg.IsCompleted
                               )                             
 
IAsyncResultオブジェクトのStateメンバーとしては、開始時間をメイン側から渡すことを前提にしています。
 
それではメイン側からBeginInvokeで非同期処理を開始してみます。なお引数の説明をもう一度しておきます。
第一成分はmyWhatToDoFuncへの引数
第2成分はmyWhatToDFuncの実行が終わった時に「更新されたIAsyncResultオブジェクト」が渡されて実行されるコールバック関数
第3成分はユーザー定義のオブジェクト(AsyncStateに保持される)
 
let iarFromMain = myWhatToDoFunc.BeginInvoke(("a",50),myCallback,DateTime.Now)
 
BeginInvokeメソッドを呼び出すことにより、myWhatToDoFuncの非同期呼び出しが開始されます。BeginInvokeメソッドはIAsyncResultオブジェクトを返しすぐに制御を戻しますので、myWhatToDoFuncメソッドが終了するまでブロックすることはありません。結果を取得するためには、EndInvokeメソッドを使います。EndInvokeメソッドを呼び出したときに非同期処理が完了していれば制御はすぐに戻りますが、まだ終わっていなければ終了までブロックします。
 
さてEndInvokeの呼び出しですが、問題はこれをどこから(メイン側からか、非同期処理側か)呼び出すかです。またEndInvokeの返り値は、非同期処理する関数の返り値となります。
 
まずはメイン側から呼び出してみます。
 
printfn "iar.IsCompleted is %A at Position 0" iarFromMain.IsCompleted 
for i in 0 .. 3 do Thread.Sleep(1); printfn "[%d]" i
let res = myWhatToDoFunc.EndInvoke(iarFromMain) //#2
printfn "iar.IsCompleted is %A at Position 2" iarFromMain.IsCompleted  
printfn "result = %A" res
 
#2の部分で呼び出すことにより、メイン側としては非同期処理の終了を待つこととなります。
 
それではもう一度コードを再掲して、実行してみます。
 
open System
open System.Threading
let  myWhatToDoDlgt = (fun (s:string,n:int) ->
                            for i in 0 .. (n - 1) do
                                Thread.Sleep(1)
                                printfn "%d :%s" i s
                            10 * n)   
let myWhatToDoFunc  = new Func<_,_> (myWhatToDoDlgt)
let myCallback =   new  AsyncCallback
                              (fun (iarInDeleg:IAsyncResult) ->
                                 printfn  "delegate end. Start Time was %A"  ((iarInDeleg.AsyncState).ToString())
                                 printfn "iar.IsCompleted is %A at Position 1" iarInDeleg.IsCompleted
                               )                           
 
let iarFromMain = myWhatToDoFunc.BeginInvoke(("a",50),myCallback,DateTime.Now)
printfn "iar.IsCompleted is %A at Position 0" iarFromMain.IsCompleted 
for i in 0 .. 3 do Thread.Sleep(1); printfn "[%d]" i
let res = myWhatToDoFunc.EndInvoke(iarFromMain)
printfn "iar.IsCompleted is %A at Position 2" iarFromMain.IsCompleted  
printfn "result = %A" res
 
実行例 
 
iar.IsCompleted is false at Position 0
[0]
[1]
0 :a
[2]
1 :a
[3]
2 :a
3 :a
4 :a
5 :a
中略
49 :a
iar.IsCompleted is true at Position 2
result = 500
delegate end. Start Time was "2009/12/24 8:21:48"
 
iar.IsCompleted is val myWhatToDoDlgt : string * int -> int
val myWhatToDoFunc : Func<(string * int),int>
val myCallback : AsyncCallback
val iarFromMain : IAsyncResult
val res : int = 500
 
>
true at Position 1
 
メイン側の処理が、非同期処理の終了は待つのですが、コールバック関数は別スレッドで実行されるので、上のような結果となってます。
 
次はコールバック関数内から、EndInvokeを呼び出す方法です。
そのためには、コールバック関数に非同期処理する関数を渡す必要があります。これには、 IAsyncResultオブジェクトのユーザー定義のオブジェクト(AsyncStateに保持される)を利用します。(前回の例では開始時間を保持させたプロパティです。)
次のようになります。
 
open System
open System.Threading
let  myWhatToDoDlgt = (fun (s:string,n:int) ->
                            for i in 0 .. (n - 1) do
                                Thread.Sleep(1)
                                printfn "%d :%s" i s
                            10 * n)   
let myWhatToDoFunc  = new Func<_,_> (myWhatToDoDlgt)
let myCallback =   new  AsyncCallback
                              (fun (iarInDeleg:IAsyncResult) ->
                                 let wtdf,stTime = ((iarInDeleg.AsyncState) :?> (Func<(string * int),int> * System.DateTime ))
                                 printfn  "delegate end. Start Time was %A"  (stTime.ToString())
                                 printfn "iar.IsCompleted is %A at Position 1" iarInDeleg.IsCompleted
                                 let res = wtdf.EndInvoke(iarInDeleg)
                                 printfn "result = %A" res
                               )                           
 
let iarFromMain = myWhatToDoFunc.BeginInvoke(("a",50),myCallback,(myWhatToDoFunc,DateTime.Now))
printfn "iar.IsCompleted is %A at Position 0" iarFromMain.IsCompleted 
for i in 0 .. 3 do Thread.Sleep(1); printfn "[%d]" i
printfn "iar.IsCompleted is %A at Position 2" iarFromMain.IsCompleted  
 
<DOS窓での実行例>
 
iar.IsComplet0 :a
ed is 1 :a
2 :a
中略
47 :a
48 :a
49 :a
delegate end. Start Time was "2009/12/24 9:02:20"
iar.IsCompleted is false at Position 0
true at Position 1
[0]
result = 500
[1]
[2]
[3]
iar.IsCompleted is true at Position 2
続行するには何かキーを押してください . . .
 
まとめると上の例では。処理が非同期的に開始され、その終了時には処理に付加しておいたコールバックデリゲイト(引数は IAsyncResultオブジェクト)が実行されます。そのコールバックデリゲイト中では必ずEndOperationNameを呼び出す必要があり、EndOperationNameは非同期処理の結果(返り値)を回収するという流れになっています。
 
なおこのBeginInvokeを利用した例もスレッドプールを利用して、登録されたメソッドを実行するという仕組みになっています
 
この方法のメリットは 「メソッドに型のあるパラメータを指定できる」、「簡単に戻り値を得ることができる」という点であり、デメリットは「優先順位付けや待機、停止など、スレッドの細かな制御が難しい」、「同時に実行できるスレッドの数が制限されている」という点です。 
 
ということで、結構ややこしいのですが、さらにこれに、例外処理、キャンセルなどの処理を加えると更にややこしくなります。このような作業を楽にしてくれるのがAsynchronous Workflowsです。これを次回から紹介していきます。
スポンサーサイト

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

コメントの投稿

非公開コメント

プロフィール

T GYOUTEN

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

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

この人とブロともになる

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