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#の正しいメソッドは何ですか?
まず第一に、あなたのユースケースでは、async { }
ブロックは必要ありません。 Async.AwaitTask
はAsync<'T>
を返すため、async { }
ブロックは、取得したAsync
オブジェクトをアンラップし、すぐに再ラップします。
不要なasync
ブロックを取り除いたので、取得したタイプと、取得したいタイプを見てみましょう。 Async<'a>
を取得し、タイプ'a
のオブジェクトが必要です。 利用可能なAsync
関数 を見ると、Async<'a> -> 'a
のような型シグネチャを持つものは Async.RunSynchronously
です。 int
とCancellationToken
の2つのoptionalパラメーターが必要ですが、これらを省略すると、探している関数シグネチャーが得られます。そして確かに、ドキュメントを見ると、Async.RunSynchronously
は f#はC#の C#のawait
に相当します。これは必要なものです。await
のようなものです(正確ではありません)。 C#のawait
は、async
関数内で使用できるステートメントですが、F#のAsync.RunSynchronously
はasync
オブジェクトを受け取り、その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:現在のスレッドをブロックせずに非同期の結果を待ちたい場合、通常は次のようなパターンの一部です。
そのパターンを書く最良の方法は次のとおりです。
let equivalentOfAwait () =
async {
let! result = someAsyncOperation()
doSomethingWith result
}
上記は、doSomethingWith
がunit
を返すことを前提としています。これは、その副作用のために呼び出しているためです。代わりに値を返す場合は、次のようにします。
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
関数(Start
、StartImmediate
、StartAsTask
、StartWithContinuations
など)のいずれかを使用できます。オン)。 Async.StartImmediate
exampleAsync.SwitchToContext
function についても少し説明しますが、これについては読みたいと思うかもしれません。しかし、私はSynchronizationContext
sに精通していないため、それ以上のことは言えません。
この状況で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