非同期ワークフロー (F#)
ここでは、F# での、非同期的 (つまり、他の作業の実行を妨げない) 計算の実行のサポートについて説明します。 たとえば、非同期計算を使用すると、他の作業の実行中にもユーザーに応答できる UI を備えたアプリケーションを作成できます。
async { expression }
解説
前の構文では、expression で表される計算が、非同期で (つまり、非同期のスリープ操作、I/O、およびその他の非同期操作が実行されるときに現在の計算スレッドをブロックせずに) 実行するように設定されています。 非同期計算は、多くの場合、現在のスレッドで実行を継続しながらバックグラウンド スレッドで開始されます。 式の型は Async<'a> であり、'a は、return キーワードを使用するときに式から返される型です。 このような式のコードは、非同期ブロックまたは async ブロックと呼ばれます。
非同期プログラミングにはさまざまな方法があり、Async クラスでは、いくつかのシナリオをサポートするメソッドが提供されます。 通常の方法では、非同期で実行する計算を表す Async オブジェクトを作成し、いずれかのトリガー関数を使用してこれらの計算を開始します。 さまざまなトリガー関数によって、非同期計算を実行するさまざまな方法が提供されます。どの方法を使用するかは、現在のスレッド、バックグラウンド スレッド、または .NET Framework タスク オブジェクトのどれを使用するか、および、計算の完了時に実行している必要がある継続関数があるかどうかによって決まります。 たとえば、現在のスレッドで非同期計算を開始するには、Async.StartImmediate を使用できます。 非同期計算を UI スレッドから開始する場合は、キーストロークやマウスの動作などのユーザー アクションを処理するメインのイベント ループがブロックされないため、アプリケーションの応答性が維持されます。
let! を使用した非同期束縛
非同期ワークフローでは、一部の式と操作は同期されています。さらに、計算処理がより長く、結果を非同期に返すように設計された式と操作もあります。 メソッドを非同期に呼び出す場合は、通常の let 束縛の代わりに、let! を使用します。 let! を使用すると、計算の実行時に、他の計算またはスレッドに対して計算を続行できます。 let! 束縛の右側が返されると、他の非同期ワークフローが実行を再開します。
let と let! との違いを次のコードに示します。 let を使用するコード行では、Async.StartImmediate や Async.RunSynchronously などを使用して、後から実行できるオブジェクトとして非同期計算を作成します。 let! を使用するコード行では、計算が開始された後、結果が使用できるようになるまでスレッドが中断され、その時点から計算が続行されます。
// let just stores the result as an asynchronous operation.
let (result1 : Async<byte[]>) = stream.AsyncRead(bufferSize)
// let! completes the asynchronous operation and returns the data.
let! (result2 : byte[]) = stream.AsyncRead(bufferSize)
let! 以外に、use! を使用して非同期束縛を実行することもできます。 let! と use! の違いは、let と use の違いと同じです。 use! では、現在のスコープを閉じるときにオブジェクトが破棄されます。 F# 言語の現在のリリースでは、値を null に初期化することは use では許可されますが、use! では許可されない点に注意してください。
非同期プリミティブ
単一の非同期タスクを実行して結果を返すメソッドは、非同期プリミティブと呼ばれます。これらのメソッドは、let! で使用するように特別にデザインされています。 F# コア ライブラリには、いくつかの非同期プリミティブが定義されています。 Web アプリケーション用のこのような 2 つのメソッドが、Microsoft.FSharp.Control.WebExtensions: WebRequest.AsyncGetResponse および WebClient.AsyncDownloadString の各モジュールに定義されています。 これらのプリミティブは両方とも、指定された URL を使用して Web ページからデータをダウンロードします。 AsyncGetResponse では WebResponse オブジェクトが生成されます。AsyncDownloadString では、Web ページの HTML を表す文字列が生成されます。
Microsoft.FSharp.Control.CommonExtensions モジュールには、非同期 I/O 操作用のプリミティブがいくつか含まれています。 Stream クラスのこれらの拡張メソッドは、Stream.AsyncRead および Stream.AsyncWrite です。
F# PowerPack には、使用可能な追加の非同期プリミティブがあります。 async ブロックに完全な本体が囲まれた関数を定義することで、独自の非同期プリミティブを作成することもできます。
他の非同期モデル用にデザインされた .NET Framework の非同期メソッドを F# 非同期プログラミング モデルで使用するには、F# Async オブジェクトを返す関数を作成します。 F# ライブラリには、これを簡単に行うための関数があります。
ここでは、非同期ワークフローの使用例を 1 つ示します。Async クラスのメソッドに関するドキュメントには、その他の例が多数用意されています。
使用例
この例では、非同期ワークフローを使用して、計算を並行して実行する方法を示します。
次のコード例では、fetchAsync 関数が、Web 要求から返された HTML テキストを取得します。 fetchAsync 関数には非同期コード ブロックが含まれています。 非同期プリミティブ (この場合は AsyncDownloadString) の結果への束縛を作成したら、let の代わりに let! を使用します。
Async.RunSynchronously 関数を使用して、非同期操作を実行し、その結果を待ちます。 1 つの例として、Async.Parallel 関数を Async.RunSynchronously 関数と共に使用して、複数の非同期操作を並列実行できます。 Async.Parallel 関数は、Async オブジェクトのリストを受け取り、並列実行する各 Async タスク オブジェクトのコードを設定し、並列計算を表す Async オブジェクトを返します。 単一の操作の場合と同様に、Async.RunSynchronously を呼び出して実行を開始してください。
runAll 関数は、3 つの非同期ワークフローを並列で起動し、これらがすべて完了するまで待機します。
open System.Net
open Microsoft.FSharp.Control.WebExtensions
let urlList = [ "Microsoft.com", "https://www.microsoft.com/"
"MSDN", "https://msdn.microsoft.com/"
"Bing", "https://www.bing.com"
]
let fetchAsync(name, url:string) =
async {
try
let uri = new System.Uri(url)
let webClient = new WebClient()
let! html = webClient.AsyncDownloadString(uri)
printfn "Read %d characters for %s" html.Length name
with
| ex -> printfn "%s" (ex.Message);
}
let runAll() =
urlList
|> Seq.map fetchAsync
|> Async.Parallel
|> Async.RunSynchronously
|> ignore
runAll()