【実験】C# HttpClientのソケット大量消費の回避
概要
ちょっとしたものを作っている過程でHttpClient
を使用した場合のソケット数について気になることがあったので
netstat
コマンドで計測しながら下記の2パターン実験しました。
公式ドキュメントに記載のあるSocketException
エラーを回避する方法ですが自分で確かめたかった次第です。
HttpClient
をusing
で囲んで使用するパターン(Badパターン)HttpClient
を静的メンバとして宣言しインスタンス使い回すパターン(Goodパターン)
正味、コード的には公式ドキュメントのまんまですがnetstat
での計測結果付きです。
HttpClientFactory
については触れないのであしからず。
あと検証環境はMacOSです。WindowsとMacOSで中でラップしてるクラスが違うっぽいので動作の違いに注意してください。
目次
参考サイト様
- HttpClient Class (System.Net.Http) #注釈 | Microsoft Docs
- Improper Instantiation antipattern - Performance antipatterns for cloud apps | Microsoft Docs
- You're using HttpClient wrong and it is destabilizing your software | ASP.NET Monsters
- .NET(Framework)のHttpClientの取り扱いには要注意という話 - Qiita
- ASCII.jp:TCPのコネクションとはなんですか? (2/2)|TCP/IPまるわかり
- 知ったかぶりをしていたソケット通信の基礎を改めて学んでみる - Qiita
環境
- Mac OS X Mojave 10.14.4
- Visual Studio Community 2019 for Mac Version: 8.0.1
- .NET Core SDK Version: 2.2.104
- C# Language Version 7.3
成果物
HttpClient
を使用したソケット消費数の計測
消費と言う言葉が適当かはわかりませんが消費と言わせてもらいます。
通常、System.Net.Http
名前空間のHttpClient Class
を使用する場合
IDisposable
が継承されているためusing
句で囲って使用する様に見えますがこれでは一度開いたソケットが閉じられません。
※正しくはソケットをクローズするまでに時間がかかりメソッドの処理が終了してもソケットが残ったままとなっている現象。
そのため、公式ドキュメントではHttpClient
を静的メンバとしてソケットを使い回すように案内されています。
詳細については下記が参考になります。
- HttpClient Class (System.Net.Http) #注釈 | Microsoft Docs
- You're using HttpClient wrong and it is destabilizing your software | ASP.NET Monsters
- .NET(Framework)のHttpClientの取り扱いには要注意という話 - Qiita
下記、パターン2種類のサンプルコードと最後に結果をスクショで提示します。
HttpClient
をusing
で囲んで使用するパターン(Badパターン)HttpClient
を静的メンバとして宣言しインスタンス使い回すパターン(Goodパターン)
実験にはIP固定の静的サイトのほうが都合良かったので自身のtodoアプリのサーバーを間借りしました。
HttpClient
をusing
で囲むパターン(Bad)
HttpClient
の実装は下記の通りです。
using
で囲って対象のサイトからHTMLのbodyを取ってきます。
HttpClientBadExample.cs
public class HttpClientBadExample { public async Task<string> RunAsync (string url) { var result = ""; using (var client = new HttpClient()) { var response = await client.GetAsync(url); result = await response.Content.ReadAsStringAsync(); } return result; } }
そして、下記がHttpClient
呼び出し元です。
for
を使用してHttpClientBadExample
クラスのRunAsync
が10回実行されるようにします。
Zikken.cs
public class Zikken { private readonly string _url; public Zikken() { _url = "http://todo.kitigai.org/zikken/"; } public async Task BadPatternA () { Console.WriteLine("悪い例1"); var badclient = new HttpClientBadExample(); for (int i = 0; i < 10; i++) { Console.WriteLine("{0}回目:{1}", i, await badclient.RunAsync(_url)); } } public async Task BadPatternB () { Console.WriteLine("悪い例2"); for (int i = 0; i < 10; i++) { var badclient = new HttpClientBadExample(); Console.WriteLine("{0}回目:{1}", i, await badclient.RunAsync(_url)); } } }
現時点の想定ではプログラムの実行が終了してもソケットが10個残ると予想しています。
結果
計測にはnetstat
コマンドを使用し、watch
コマンドで確認しました。
結果は以下の通りです。
想定通りソケットが10個使用されておりプログラムが終了した状態でも残ったままです。
BadPatternAもBも結果は変わらずです。
実行前 | 実行後 |
---|---|
簡単にindex.html
のbody
を取ってるだけなので10〜20秒くらいでソケットは閉じられます。
HttpClient
を静的メンバとして使い回すパターン(Good)
今度は先程と違いHttpClient
を静的メンバとしてインスタンス間で使いまわします。
HttpClientGoodExample.cs
public class HttpClientGoodExample { private static readonly HttpClient _client = new HttpClient(); public async Task<string> RunAsync (string url) { var response = await _client.GetAsync(url); return await response.Content.ReadAsStringAsync(); } }
こちらは先程と同様にHttpClient
の呼び出し元です。
for
を使用してHttpClientGoodExample
クラスのRunAsync
が10回実行されるようにします。
Zikken.cs
public class Zikken { private readonly string _url; public Zikken() { _url = "http://todo.kitigai.org/zikken/"; } public async Task GoodPatternA () { Console.WriteLine("良い例1"); var goodclient = new HttpClientGoodExample(); for (int i = 0; i < 10; i++) { Console.WriteLine("{0}回目:{1}", i, await goodclient.RunAsync(_url)); } } public async Task GoodPatternB () { Console.WriteLine("良い例2"); for (int i = 0; i < 10; i++) { var goodclient = new HttpClientGoodExample(); Console.WriteLine("{0}回目:{1}", i, await goodclient.RunAsync(_url)); } } }
このパターンは想定ではインスタンス間でHttpClient
を使いまわしているはずなのでソケットは10個よりも少なくなると予想しています。
結果
計測も先程と同様にnetstat
をwatch
コマンドで確認です。
想定通り、Badパターンと違いソケット数が減少しました。
GoodPatternAもBも結果は変わらずです。
※試行回数が少ない+簡単なコードなので1ソケットと言う結果になったと思われ。
実行前 | 実行後 |
---|---|
結果比較
結果はもう提示してしまっているのでこれ以上ありませんが比較用に貼っておきます。
実行前 | using パターン |
静的メンバパターン |
---|---|---|
雑感
昨日、コナンの映画見てきたんですが「これが...コナン...?」って感想でした。