ASP.NET Core 6以降のMinimal APIで.Map*に渡すdelegateはasyncにするべきか
もともとの疑問
ASP.NET Core 6以降でMinimal APIを利用するときに、どういう関数を渡すべきか、ということに悩んでいた。サンプルを読んでいるとC#では async 修飾子を付与して、関数を渡しているケースが多い。いくつか読んでいても async は重要だぞ、ということが書いてあるように見える。 調べてみた
C#で登場する async 修飾子をつけることによって、コンパイラがよしなに解釈してくれて専用のクラス(IAsyncStateMachine)が自動的に生成されるらしい。CPUバウンド、I/Oバウンドな処理で非同期処理で登場するのと、これをどこかで利用するとほぼ必然的にすべてが async になる。以下の引用の通りだけど、どこかで停止させてしまうとデッドロックが発生する可能性があるらしい。
You may be tempted to “stop” this by blocking in your code using Task.Result or Task.Wait, converting just a small part of the application and wrapping it in a synchronous API so the rest of the application is isolated from the changes. Unfortunately, this is a recipe for creating hard to track deadlocks.
結論としてはC#においては、MapGet に渡す delegate に async 修飾子を付与するのは必然ということになるのかな。
F#はどうなるのか
翻ってF#の話。F#で登場する Async 型はF#特有の型で、非同期式( async 式)というものを利用して記述することになる。C#の async 修飾子とは関連がない様子。
F# でほとんどの非同期コードを記述する場合は、より簡潔で構成性が高く、.NET タスクに関連する特定の注意が回避される F# 非同期式が推奨されます。
Minimal APIに関連するissueでも言及されているけど、F#の Async 型は固有の型のよう。
そして、恐らくはC#の async 修飾子に相当するものはF#にはなさそう…? 少なくともドキュメント上いろいろ探してみても見当たらない(少なくとも私の現在の理解では探し出すことができていない)ので、一旦ないということにしておく。
F#の非同期プログラミングについて読んでいると、F#の async 式はあくまでも「非同期」であることのみを意味していて、「並列」や「バックグラウンド処理」であることを保障しないとのこと。
C#の非同期プログラミングと異なっていて、F#の非同期式は仕様でしかないので明示的に自分で開始させる必要がある。実際、これはASP.NET Coreの戻り値でもそうなっているみたいで、以下のコードのように <Async<IResult>> を返してしまうと処理は正常に終了するけど、レスポンスには何も含まれなくなってしまう。そのため、 <Task<IResult>> を返す必要がある。
code:fsharp
app.MapGet("/async-ping", Func<Async<IResult>> Handlers.JustReturnAsyncPong)
|> ignore
app.MapGet("async-task-ping", Func<Task<IResult>> Handlers.JustReturnAsyncToTaskPong)
|> ignore
戻り値の型として、Async型はないということかな。
そうなると最終的に分からないのは Task を返すべきなのか、内部的に Async 式を実行(?)してしまって結果を同期的に取得してしまってもいいのか、ということ。あまりドキュメントを読んでいても強く名言されている様子はないし、ブログやStackOverflowなどでも話題になっていないので、どちらでもいい…のかな。
結論
F#の場合、Async型を用いて非同期プログラミングをしている場合に限って言えば、Async式の実行結果を同期的に取得して、単純なIResult型を返してしまってもよいように見える。そもそもC#においてスレッドのデッドロックが発生するというのが問題で、async/awaitを使ったならそれを最後まで引き継ぐのが重要という話らしく、F#においてその問題はない(async/awaitではないので)ため、どちらでもよい、というのが最終的な結論になるのかなと思った。 C#のデッドロックについては以下に詳細な回答が存在する。
それはそれとして、以下の文章はあとで読む。