非同期式とは?
非同期式を使用することで非同期的、つまり他のプログラムの処理を止めずに実行することが出来ます
非同期に処理を行う事で負荷のかかる処理や時間のかかる処理を同時進行で行い、メインプログラムに影響が起きないようにすることが出来ます。
F#ではasync{}と記述すると{}の中が非同期で実行されます
構文:async{非同期式...}
async{
非同期式...
}
非同期のバインド(割り当て)
一部の式・操作は同期的に行われるため非同期的に行う場合はlet!バインドを使用します。
またlet!やdo!はただ実行するだけでなく非同期式の計算中に他の計算やスレッドで実行を続行できます。F#では「!」を付けるだけで式・結果が戻ってくるまで待機してくれます。
C#やJavaを触っていた人には「自動でawaitになる」と考えると分かりやすいかと思われます。
以下はletとlet!の違いです
//ストリームより非同期に読み取るstream.AsyncRead()を例にします
let result1:Async<byte[]>=stream.AsyncRead()
//->result1はAsyncRead()を非同期実行する処理として保存される
let! result2:byte[]=stream.Asyncread()
//->result2はAsyncRead()を非同期実行し値を戻す
非同期式の実行
以下のキーワードを使用して非同期式を実行することが出来ます
また任意でタイムアウトまでの時間・キャンセル用のトークンを設定することもできます
Async.RunSynchronously
Async.RunSynchronouslyを使用することで非同期式を同期的に行います
//構文:Async.RunSynchronously <非同期式> (タイムアウトするまでの時間) (キャンセルトークン)
//非同期式を受け取り、値を戻す
let asyncex1()=
async{
printfn "asyncexが呼び出されました"
return 0
}
//呼び出し
let back=asyncex()|>Async.RunSyncronously
//backには0が入る
Async.Start
Async.Startを使用すると非同期式を実行し、結果を待たず次の処理に移ります。
//構文:Async.Start <非同期式> (タイムアウトするまでの時間) (キャンセルトークン)
//例:2秒後に"called"を戻す非同期式を実行
let returncalled()=
async{
printfn "2.."
do! Async.Sleep 1000
printfn "1.."
do! Async.Sleep 1000
return "called"
}
//呼び出し
let msg=returncalled()|>Async.Start
現在使用しているスレッドより非同期処理を実行します
//構文:Async.StartImmediate <非同期式> (キャンセルトークン)
//例:"OK"を戻す非同期式を実行する
printfn "非同期式実行..."
let back=async{
return "OK"
}|>Async.StartImmediate
printfn "%s" back //OKと表示される
Async.StartChild
非同期式内で非同期式を開始します
Async.Startと同様に結果を待たず次の処理に移ります
//構文:Async.StartChild <非同期式> (タイムアウトするまでの時間)
//例:Async.Startで取り上げた非同期式「returncalled」を2回呼び出す非同期式
let twicereturn()=
async{
let! back1=returncalled()|>Async.StartChild
let! back2=returncalled()|>async.StartChild
do! Async.Sleep 2000
printfn "twicereturn completed"
}
//呼び出し
twicereturn()|>Async.RunSynchrnously
Async.StartWithContinuations
成功時の処理、エラー発生時の処理、キャンセル時の処理を指定して非同期式を実行します
//構文:Async.StartWithContinuations <実行する非同期式> <成功時の処理> <例外時の処理> <キャンセル時の処理>
//例:成功、失敗、キャンセル時にメッセージを表示する非同期式
let asyncex2(x)=Async.StartWithContinuations(
async{sprintf "return %d" x},
fun _->printfn "成功",
fun _->pirntfn "例外発生",
fun _->printfn "キャンセルされました"
)
非同期式の停止・キャンセル
以下のキーワードを使用することで非同期式を一時停止させたり終了の命令を出すことが出来ます
Async.Sleep
Async.Sleepを使用することで処理を停止することが出来ます
//構文:Async.Sleep 時間(ms単位orTimeSpan)
//引数から0までカウントダウンをする非同期式
let countdown(x:int)=
async{
for i=x downto 0 do
printfn "残り%d秒" i
do! Async.Sleep 1000//Async.Sleepも非同期なのでdo!を挟む
printfn "終了"
}
//呼び出し
countdown(10)|>Async.RunSynchronously
Async.CancelDefaultToken
キャンセルトークンが設定されていない非同期式の処理を全てキャンセルします
//構文:Async.CancelDefaultToken()
//例:Async.Sleepで使用した非同期式を途中でキャンセルする
countdown(10)|>Async.Start
System.Threading.Thread.Sleep 3000
Async.CancelDefaultToken()
//出力結果:
//残り10秒
//残り9秒
//残り8秒
//(ここでキャンセルされる)//
Async.OnCancel
非同期式のキャンセル時の処理を設定します
実行中の処理がある場合はそのまま行い、その後の処理を全てキャンセルします
Async<IDispose>型のためuse!キーワードで割り当てする必要があります
(キャンセル時の処理にはラムダ式を使用してください)
//use! <バインド名>=Async.OnCancel(キャンセル時の処理...)
//例:引数で渡された値の時間(秒)カウントダウンし終了、キャンセル時はメッセージを表示する非同期式
let waiting(x)=
async{
use! whencancel=Async.OnCancel( //キャンセルしたときの処理をwhencancelに割り当て
fun()-> //処理は1行のみ認識されるっぽいのでラムダ式にします...
printfn "キャンセルされました"
printfn "途中ですが終了します"
)
do! Async.Sleep (x*1000)
printfn "処理完了。所要時間:%d秒" x
}
//呼び出し
waiting(5)|>Async.Start //5秒のカウントダウン
System.Threading.Thread.Sleep 3000 //3秒待機
Async.CancelDeafultToken() //非同期式のキャンセル
Async.TryCancelled
第一引数の非同期式を実行し、キャンセル時には第二引数の処理を実行します
//構文:Async.TryCancelled <非同期処理> <キャンセル時の処理>
//例:1から引数までカウントアップし、キャンセル時にはメッセージを出力する
let countup(x)=
async{
for i=1 to x do
do! Async.Sleep 1000
printfn "%d" i
printfn "complete"
}
//呼び出し
Async.TryCancelled(
countup(4),
fun _->
printfn "キャンセルを検知"
printfn "キャンセルします"
)|>Async.Start
System.Threading.Thread.Sleep 2000
Async.CancelDefaultToken()
//出力結果:
//1
//2
//キャンセルを検知
//キャンセルしまキャンセルします
複数の非同期式
Async.Sequence
リスト、配列、シーケンス内の非同期式を順番に実行するように設定します
//構文:Async.Sequence <非同期式のコレクション>
//例:Async.Sleepで使用した非同期式「countdown」を順番に実行する
let seqasync=[countdown 3;countdown 10;countdown 7]|>Async.Sequence
//seqasyncを呼び出す
seqasync|>Async.RunSyncrnously
//3秒->10秒->7秒と順番でカウントダウンを行う
Async.Parallel
リスト、配列、シーケンス内の非同期式を同時実行するように設定します
//構文:Async.Parallel <非同期式のコレクション>
//例:Async.Sleepで使用した非同期式「countdown」を並列実行する
let paraasync=[|countdown 3;countdown 10;countdown 7|]|>Async.Parallel
//paraasyncを呼び出す
paraasync|>Async.RunSynchrnously
//同時に実行するためAsync.Sequenceよりも早く処理が終わります
Async.Choice
オプション型を返す複数の非同期式を同時実行し、最初に処理が完了した式の値を戻すようにします
//構文:Async.Choice <非同期式のコレクション>
//例:引数x秒待機し,x*1000を戻す非同期式「taiki」
let taiki(x:int)=async{
do! Async.Sleep (x*1000)
return (x*1000)
}
//taikiをOption型で返す非同期式「taikioption」
let taikioption(x:int)=async{return (Some taiki(x))}
//呼び出し(リスト型で呼び出します)
let back1=[taikioption 2;taikioption 4;taikioption 3]
|>Async.Choice
|>Async.RunSynchrnously
printfn "%d" back.Value //最も早く終わった非同期式の戻り値(=2)を表示
//Seq.mapやArray.map、List.mapを使用して
//コレクションをマッピングしてから値を戻すようにすることもできます
//呼び出し2(シーケンス型で呼び出します)
let back2=seq{32;18;21}
|>Seq.map(fun x ->async{return (Some taiki(x))})
//Seq.mapを使ってオプション型を返す非同期式に加工してます
|>Async.Choice
|>Async.RunSynchrnously
printfn "%d" back2.Value //最も早く終わった非同期式の戻り値(=18)を表示