ねののお庭。

かりかりもふもふ。

ASP.NET CoreなどのサーバーサイドC#におけるasync/awaitについて。

クライアントサイドでasync/await利用するときってだいたいメインスレッドで長々計算するとUI固まってアカンので別のスレッドでスレッド回すぜ!っていっているケースが多いと思います。 そのノリでサーバサイドのC#を書くと、疑問にぶち当たって、 別にサーバで計算回す分にはUI固めることもないしasync/awaitの意味なくない? となります。意味は当然あるので、すこし整理をします。

そもそもasync/awaitを利用するケース

async/awaitを使うときってだいたい2つのケースにわけられて

  • 別のスレッドに計算を任せるケース
  • IO待ちのケース

だと思います。それぞれ見ていきます。

別のスレッドに計算を任せるケース

GUIアプリではメインスレッドで長々計算すると固まるので、Task.Run()等を走らせて別スレッドに逃がしますが、ASP.NET Coreなどを利用してサーバーサイドを書いている時はGUI固めるから〜とかいう理由で別スレッドに逃がす必要がありません。そのためTask.Runなどの自分でスレッドを切ったりしてasync/awaitする必要は基本的にはありません。なんならサーバーサイドで下手に別スレッドを利用することは性能低下に繋がることもあるようです。 なのでなにかそれなりの理由がない限り、サーバーサイドでながなが計算するためにスレッドを切る、みたいなことはしないでOKということです。

IO待ちのケース

サーバサイドC#のasync/awaitの最大活用方は別スレッドに計算投げるとかではなく、IO待ちにあります(と私は思います)。 ファイルに書き込んだり、httpリクエスト飛ばしてレスポンス待っている間ってCPU使っていませんよね。でこの間CPUを専有する必要はないわけです。計算してないならほかのリクエスト処理するなりCPUの計算資源は活用するべきです。なのでIO待ちが発生している時のawaitでは、一度使っているスレッドが開放されます。開放されたスレッドでは別のリクエストを処理したりします。そしてawaitが終わって処理に戻ってくるときには、新たにスレッドが割り当てられて(awaitする前に使用していたスレッドとは限らないということ)、await以降の処理が実行されることになります。このため、CPUを専有することなくIO待ちすることができるため、計算していないスレッドを作らず効率的にCPUを回すことができます。素晴らしい。

まとめ

サーバサイドではクライアントC#のノリで別スレッドに計算投げるためのasync/awaitには意味があまりなく、IO待ちでのasync/awaitで意味を発揮するということでした。

参考

ざっくり書きましたが以下にもっといろいろ書いてあります。軽く目を通すといいかもしれません。 https://msdn.microsoft.com/ja-jp/magazine/dn802603.aspx

補足(?)

ASP.NET Coreや.NET Coreコンソールではasync/await後にどのスレッドに投げられるかわかりません。しかしWPFやUnityでは必ずawait後の処理はメインスレッドで行われます。ここれへんの違いは、SynchronizationContextが鍵になっていて、ASP.NET CoreやコンソールではSynchronizationContext.Currentはnullが返ってきます。しかしWPFやUnityではnullではなく、ちゃんとインスタンスが返ってきます。await後にどのスレッドに処理が投げられるかはSynchronizationContext.Currentで返ってくるインスタンスに依存していて、たとえばWPFではこのような実装になっています。await後の後続の処理は.postに投げられるのですが、_dispatcher.BeginInvokeでメインスレッドに戻されていることがわかります。ASP.NET Coreやコンソールではこのような実装がされていないのでもといたスレッドにもどってくる、的な挙動はしないのです。 でここからは推測なのですが、.Currentがnullの場合はどういう挙動するか、ていう点はSynchronizationContext自身が実装している挙動、つまりスレッドプールに投げられる実装が呼ばれているものと思われます。