次の方法で共有


非同期式

この記事では、非同期式の F# でのサポートについて説明します。 非同期式は、計算を非同期的に実行する 1 つの方法を提供します。つまり、他の作業の実行をブロックしません。 たとえば、非同期計算を使用して、アプリケーションが他の作業を実行する際にユーザーに対して応答性が維持される UI を持つアプリを作成できます。 F# 非同期ワークフロー プログラミング モデルを使用すると、ライブラリ内のスレッド遷移の詳細を非表示にしながら、関数型プログラムを記述できます。

非同期コードは、.NET タスクを直接作成 するタスク式を使用して作成することもできます。 .NET タスクを作成または使用する .NET ライブラリと広範囲に相互運用する場合は、タスク式を使用することをお勧めします。 F# でほとんどの非同期コードを記述する場合、F# 非同期式は簡潔で構成性が高く、.NET タスクに関連する特定の注意事項を回避するため、推奨されます。

構文

async { expression }

注釈

前の構文では、 expression によって表される計算が非同期的に実行されるように設定されています。つまり、非同期スリープ操作、I/O、およびその他の非同期操作が実行されるときに、現在の計算スレッドをブロックしません。 非同期計算は、多くの場合、現在のスレッドで実行が続行されている間、バックグラウンド スレッドで開始されます。 式の型は Async<'T>です。ここで、 'T は、 return キーワードが使用されるときに式によって返される型です。

Async クラスには、いくつかのシナリオをサポートするメソッドが用意されています。 一般的な方法は、非同期的に実行する計算または計算を表す Async オブジェクトを作成し、トリガー関数のいずれかを使用してこれらの計算を開始することです。 使用するトリガーは、現在のスレッド、バックグラウンド スレッド、または .NET タスク オブジェクトのどちらを使用するかによって異なります。 たとえば、現在のスレッドで非同期計算を開始するには、 Async.StartImmediateを使用できます。 UI スレッドから非同期計算を開始する場合、キーストロークやマウス アクティビティなどのユーザー アクションを処理するメイン イベント ループをブロックしないため、アプリケーションの応答性が維持されます。

let! を使用した非同期バインディング

非同期式では、一部の式と操作は同期であり、一部は非同期です。 メソッドを非同期的に呼び出すときは、通常の let バインドではなく、 let!を使用します。 let!の効果は、計算の実行中に他の計算またはスレッドで実行を続行できるようにすることです。 let! バインドの右側が戻った後、非同期式の残りの部分は実行を再開します。

次のコードは、 letlet!の違いを示しています。 letを使用するコード行は、Async.StartImmediateAsync.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! は、F# 非同期計算 Async<T> 直接待機するためにのみ使用できます。 他の種類の非同期操作を間接的に待機できます。

  • .NET タスク、 Task<TResult> 、および非ジェネリック Taskを組み合わせることにより、 Async.AwaitTask
  • .NET 値タスク、 ValueTask<TResult> 、および非ジェネリック ValueTask.AsTask() および Async.AwaitTask
  • F# RFC FS-1097 で指定された "GetAwaiter" パターンに続く任意のオブジェクトを、task { return! expr } |> Async.AwaitTaskと組み合わせて使用します。

制御フロー

非同期式には、 for .. in .. dowhile .. dotry .. with ..try .. finally ..if .. then .. elseif .. then ..などの制御フローコンストラクトを含めることができます。 同期的に実行される with ハンドラーと finally ハンドラーを除き、非同期コンストラクトをさらに含めることができます。

F# 非同期式では、非同期 try .. finally ..はサポートされていません。 この場合は 、タスク式 を使用できます。

use バインドと use! バインド

非同期式内では、 use バインディングは IDisposable型の値にバインドできます。 後者の場合、破棄クリーンアップ操作は非同期的に実行されます。

let!に加えて、use!を使用して非同期バインディングを実行できます。 let!use!の違いは、letuseの違いと同じです。 use!の場合、オブジェクトは現在のスコープの終了時に破棄されます。 F# の現在のリリースでは、 use! では、 use が行っている場合でも、値を null に初期化することはできません。

非同期プリミティブ

1 つの非同期タスクを実行し、結果を返すメソッドは 非同期プリミティブと呼ばれ、 let!で使用するために特別に設計されています。 F# コア ライブラリには、いくつかの非同期プリミティブが定義されています。 Web アプリケーション用の 2 つのこのようなメソッドは、モジュール FSharp.Control.WebExtensionsで定義されています。 WebRequest.AsyncGetResponseHttpClient.GetStringAsync (F# の非同期モデルとの互換性のために Async.AwaitTask でラップされます)。 どちらのプリミティブも、URL を指定して Web ページからデータをダウンロードします。 AsyncGetResponseSystem.Net.WebResponse オブジェクトを生成し、 GetStringAsync は Web ページの HTML を表す文字列を生成します。

FSharp.Control.CommonExtensions モジュールには、非同期 I/O 操作用のいくつかのプリミティブが含まれています。 System.IO.Stream クラスのこれらの拡張メソッドはStream.AsyncReadされ、Stream.AsyncWrite

また、本体が非同期式である関数またはメソッドを定義することで、独自の非同期プリミティブを記述することもできます。

F# 非同期プログラミング モデルを使用して他の非同期モデル用に設計された .NET Framework の非同期メソッドを使用するには、F# Async オブジェクトを返す関数を作成します。 F# ライブラリには、これを簡単に実行できる関数があります。

ここでは、非同期式を使用する例の 1 つを示します。 Async クラスのメソッドに関するドキュメントには、他にも多数あります。

この例では、非同期式を使用してコードを並列で実行する方法を示します。

次のコード例では、 fetchAsync 関数は、Web 要求から返された HTML テキストを取得します。 fetchAsync関数には、非同期のコード ブロックが含まれています。 非同期プリミティブの結果にバインドが行われると、この場合はAsyncDownloadStringletの代わりにlet!が使用されます。

関数 Async.RunSynchronously を使用して非同期操作を実行し、その結果を待ちます。 たとえば、 Async.Parallel 関数を Async.RunSynchronously 関数と共に使用することで、複数の非同期操作を並列で実行できます。 Async.Parallel関数は、Async オブジェクトの一覧を取得し、並列で実行する各Async タスク オブジェクトのコードを設定し、並列計算を表すAsync オブジェクトを返します。 1 回の操作と同様に、 Async.RunSynchronously を呼び出して実行を開始します。

runAll関数は、3 つの非同期式を並列で起動し、すべてが完了するまで待機します。

open System.Net
open Microsoft.FSharp.Control.WebExtensions
open System.Net.Http

let urlList = [ "Microsoft.com", "http://www.microsoft.com/"
                "MSDN", "http://msdn.microsoft.com/"
                "Bing", "http://www.bing.com"
              ]

let fetchAsync(name, url:string) =
    async {
        try
            let uri = new System.Uri(url)
            let httpClient = new HttpClient()
            let! html = httpClient.GetStringAsync(uri) |> Async.AwaitTask
            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()

こちらも参照ください