しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

ASP.NET の Async SessionState Module を試してみる

あまり使うつもりがなかったというか、対応したセッションプロバイダーが In-Memory と SQL Server ぐらいしかなかったので触れていなかったですが、ASP.NET 4.6.2 から非同期対応の SessionState Module が使えるようになっています。

全然情報を見なかったのと、Cosmos DB のプロバイダーが出たので調べました。

単純に Task ベースで書き直されたという感じですが、モダンなストレージクライアントでは Task を使うのが当たり前という状況なので、新しいプロバイダーは作りやすくなったと思います。

ちなみに 1.1.0 から非同期以外にロックなしのセッションに対応しています。以下のブログで紹介はされていますが、このタイトルで SessionState Module の新機能が紹介されているとか気が付かないです。

ASP.NET のセッションはストレージから取得する時にロックをかけて、排他制御するように実装されています。それによって、同じセッションのリクエストが複数来た場合に、どれかで待ち時間が発生するとロックが解除されるまで待機するようになっています。

何らかの理由でロックが解除されない場合には、AcquireRequestState の内部で最長 125 秒の待ちが発生します。この挙動はこれまで変更することが出来ませんでしたが、Async SessionState Module の 1.1.0 で設定が追加されました。

appSettings に aspnet:AllowConcurrentRequestsPerSession を追加すると、ロックされないようになります。

<appSettings>
  <add key="aspnet:AllowConcurrentRequestsPerSession" value="true"/>
</appSettings>

実際に確認するために、適当にアクションを用意しました。セッションプロバイダーとしては Async SessionState Module に対応した Cosmos DB を利用します。

Task.Delay を使って 20 秒ほど待たせる、特に説明することのないアクションです。

public async Task<ActionResult> Index()
{
    Session["test"] = DateTime.Now.ToString();

    await Task.Delay(TimeSpan.FromSeconds(20));

    return View();
}

このサンプルを使って、AllowConcurrentRequestsPerSession の設定によってどのように挙動が変わるかを簡単に確認してみます。

AllowConcurrentRequestsPerSession = false

アプリケーションを実行し、待機中の時点で Cosmos DB に格納されたデータを表示すると、locked = true となっていて確かにセッションがロックされていることが分かります。これまで通りの挙動です。

20 秒が経過し、アクションの実行が完了すると locked = false となりロックが解除されました。

ちゃんとロックされていることが確認できたので、実際にロック中に別リクエストを投げてみたところ、想定通りに別リクエストでは 20 秒の待ち時間が発生しました。

1 つのリクエストが詰まると、同じセッションのリクエスト全てで時間がかかってしまうわけです。ASP.NET ではクッキーを持っていればデフォルトでセッションを復元するので、困った挙動となります。

AllowConcurrentRequestsPerSession = true

設定を有効にして、同じようにリクエストを投げると、当然ながら locked は常に false のままとなり、ロックはかかりません。なので、自由にセッションの読み書きが行えるようになっています。

先ほどと同様に待機中にリクエストを投げても、待ちが発生することなく完了します。

これまでは一貫性が保証されていたセッションをロックなしにすると、よくある非同期での問題が発生することになるので、セッションを使っている部分をちゃんと理解して設定を有効にする必要があるでしょう。

とはいえ、最近の Web アプリケーションは同時リクエスト多いですし、使いどころはありそうです。