web-dev-qa-db-ja.com

戻り値の型がTask <T>の非同期C#メソッドをF#で正しく待機しています

F#からC#ライブラリを利用できるようにしたいと思います。ほとんどの場合、これは非常に簡単です。ただし、Task<T>を返す関数を呼び出そうとすると、戻り値を取得できません。

したがって、次の定義を持つC#メソッドがあります。

public async Task<TEvent> ReadEventAsync<TEvent>(string streamName, int position) where TEvent: class

そして、私は次のようにF#からこのメソッドを消費しようとしています:

let readEventFromEventStore<'a when 'a : not struct> (eventStore:IEventStoreRepository) (streamName:string) (position:int) = 
     async {
            return eventStore.ReadEventAsync(streamName, position) 
            |> Async.AwaitTask
            }

IEventStoreRepositoryのインスタンスと、イベントを取得したいストリーム名を使用して、この関数を部分的に適用します。

let readEvent = readEventFromEventStore eventStore streamName

次に、最後に、残りのパラメーターを適用します。

let event = readEvent StreamPosition.Start

eventの値を取得すると、予想していたFSharpAsync<object>からのTではなくTask<T>になります。

戻り値の型がTask<T>でC#で記述されたasyncメソッドを呼び出し、Tの値にアクセスするF#の正しいメソッドは何ですか?

18
David Brower

まず第一に、あなたのユースケースでは、async { }ブロックは必要ありません。 Async.AwaitTaskAsync<'T> を返すため、async { }ブロックは、取得したAsyncオブジェクトをアンラップし、すぐに再ラップします。

不要なasyncブロックを取り除いたので、取得したタイプと、取得したいタイプを見てみましょうAsync<'a>を取得し、タイプ'aのオブジェクトが必要です。 利用可能なAsync関数 を見ると、Async<'a> -> 'aのような型シグネチャを持つものは Async.RunSynchronously です。 intCancellationTokenの2つのoptionalパラメーターが必要ですが、これらを省略すると、探している関数シグネチャーが得られます。そして確かに、ドキュメントを見ると、Async.RunSynchronouslyは f#はC#のawaitに相当します。これは必要なものです。 C#のawaitのようなものです(正確ではありません)。 C#のawaitは、async関数内で使用できるステートメントですが、F#のAsync.RunSynchronouslyasyncオブジェクトを受け取り、そのasyncオブジェクトの実行が終了するまで現在のスレッドをブロックします。この場合、まさにこれがあなたが探しているものです。

let readEventFromEventStore<'a when 'a : not struct> (eventStore:IEventStoreRepository) (streamName:string) (position:int) =
    eventStore.ReadEventAsync(streamName, position) 
    |> Async.AwaitTask
    |> Async.RunSynchronously

それはあなたが探しているものを手に入れるはずです。また、必要な関数の関数シグネチャを見つけて、そのシグネチャを持つ関数を探す手法に注意してください。それは将来多くの人を助けるでしょう。

更新:コメントで私の間違いを指摘してくれたTarmilに感謝します:Async.RunSynchronously isnotC#のawaitと同等です。これは非常に似ていますが、RunSynchronouslyが現在のスレッドをブロックするため、注意すべき重要な微妙な点がいくつかあります。 (GUIスレッドで呼び出したくありません。)

更新2:現在のスレッドをブロックせずに非同期の結果を待ちたい場合、通常は次のようなパターンの一部です。

  1. 非同期操作を呼び出す
  2. 結果を待つ
  3. その結果で何かをする

そのパターンを書く最良の方法は次のとおりです。

let equivalentOfAwait () =
    async {
        let! result = someAsyncOperation()
        doSomethingWith result
    }

上記は、doSomethingWithunitを返すことを前提としています。これは、その副作用のために呼び出しているためです。代わりに値を返す場合は、次のようにします。

let equivalentOfAwait () =
    async {
        let! result = someAsyncOperation()
        let value = someCalculationWith result
        return value
    }

または、もちろん:

let equivalentOfAwait () =
    async {
        let! result = someAsyncOperation()
        return (someCalculationWith result)
    }

これは、someCalculationWithが非同期操作ではないことを前提としています。代わりに、2つの非同期操作をチェーンする必要があり、2番目の操作が最初の操作の結果を使用する場合(または、ある種のシーケンスで3つまたは4つの非同期操作を使用する場合)、次のようになります。

let equivalentOfAwait () =
    async {
        let! result1 = someAsyncOperation()
        let! result2 = nextOperationWith result1
        let! result3 = penultimateOperationWith result2
        let! finalResult = finalOperationWith result3
        return finalResult
    }

let!の後にreturnが続くことはreturn!とまったく同じであるという点を除いて、次のように記述したほうがよいでしょう。

let equivalentOfAwait () =
    async {
        let! result1 = someAsyncOperation()
        let! result2 = nextOperationWith result1
        let! result3 = penultimateOperationWith result2
        return! (finalOperationWith result3)
    }

これらの関数はすべてAsync<'T>を生成します。ここで、'Tは、asyncブロックの最後の関数の戻り値の型になります。これらの非同期ブロックを実際に実行するには、すでに述べたようにAsync.RunSynchronouslyを実行するか、さまざまなAsync.Start関数(StartStartImmediateStartAsTaskStartWithContinuationsなど)のいずれかを使用できます。オン)。 Async.StartImmediate exampleAsync.SwitchToContext function についても少し説明しますが、これについては読みたいと思うかもしれません。しかし、私はSynchronizationContextsに精通していないため、それ以上のことは言えません。

20
rmunn

この状況でasync計算式を使用する代わりに(F#がC#タスクベースのXxxAsyncメソッドを呼び出す)、次の場所からtask計算式を使用することもできます。

https://github.com/rspeele/TaskBuilder.fs

Giraffe F#Webフレームワークは、多かれ少なかれ同じ理由でtaskを使用します。

https://github.com/giraffe-fsharp/Giraffe/blob/develop/DOCUMENTATION.md#tasks

1
Andrew Webb