質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

ASP.NET

ASP.NETは動的なWebサイトやWebアプリケーション、そしてWebサービスを構築出来るようにする為、Microsoftによって開発されたウェブアプリケーション開発フレームワークです。

Q&A

解決済

3回答

4337閲覧

ASP.NETでHttpClientを使う場合も単なるstatic化で良いのでしょうか

yamaguri

総合スコア13

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

ASP.NET

ASP.NETは動的なWebサイトやWebアプリケーション、そしてWebサービスを構築出来るようにする為、Microsoftによって開発されたウェブアプリケーション開発フレームワークです。

1グッド

1クリップ

投稿2017/10/13 07:31

###前提

ASP.NETのプログラムから、別サーバのAPIをコールするのにHttpClientクラスを使おうと思っています。
APIはユーザの操作でコールするので、短期間に複数回コールする可能性があります。

HttpClientには下記URLのような仕様があり、対策としてstatic化が有効だと分かりました。
開発者を苦しめる.NETのHttpClientのバグと紛らわしいドキュメント
.NETのHttpClientの取り扱いには要注意という話 - Qiita

###質問内容

知識不足で申し訳ないのですが、ASP.NETでHttpClientを使用する場合も、単純にstatic変数で保持すれば良いのでしょうか?

####気になる点

  • スレッドセーフとあるので、複数スレッドから使い回しても問題なさそうに思えます
  • ASP.NETのstatic変数で持つと、サーバが再起動でもしなければずっと保持し続けることになると思いますが、問題ないのでしょうか
bochan2👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/10/13 08:47

どのような頻度で呼び出すのでしょう? 参考にされている前者の記事にリンクが張ってあった https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/ のコード例のように for ループを回して HttpClient の初期化を繰り返すようなメチャなことをしなければ問題なさそうな気がするのですが・・・
yamaguri

2017/10/13 09:15 編集

forループでAPIコールを含む処理を回す場合もあるので、リンク先のコード例に近い状態になるかもしれません。処理自体は右から左へAPIで受け渡すだけの単純なもので、ループカウンタも100以上ある想定です。本来ならAPIを使わない形を取るべきだと思いますが、別システムなのでAPIコール以外が取りづらいです。
退会済みユーザー

退会済みユーザー

2017/10/13 11:19

forループでAPIコールを含む処理を回す場合もあるので ← ループで HttpClient を初期化して、使って、Dispose するのを繰り返すのではなく、一つの要求を処理する際一度だけ初期化してそれを使いまわすようなことではダメなんですか? 試したわけではないので、それでは socket の浪費防止には意味がないのかもしれませんが・・・
退会済みユーザー

退会済みユーザー

2017/10/13 11:21 編集

ASP.NET はユーザーからの要求ごとにスレッドプールからスレッドを取得して処置するマルチスレッドアプリですので、単一の HttpClient のインスタンスを異なるユーザーが共有してどういうことになるか・・・質問者さん独自の条件とかいろいろあるでしょうから自分は分かりません。好ましからざる副作用がありそうで、できればそういうことをしないで済む方向を検討した方がよさそうな気がします。(気がするだけで、確証はないですが)
yamaguri

2017/10/14 15:34

すみません、誤解を与える表現でした。確かに要求のなかにforループがありますが、static化しない場合は要求ごとにインスタンスを生成します。ただ、やはり要求の頻度が多ければ同状況になると思いますので、static化したいと考えました。副作用も分からないので、いったんstatic化しない方向で実装し、様子を見てみます。
退会済みユーザー

退会済みユーザー

2017/10/16 02:25

> いったんstatic化しない方向で実装し、様子を見てみます。 ← それが良いと思います。ここでの今までの話と追加情報を回答欄にまとめて書いておきます。
guest

回答3

0

ベストアンサー

Azure Architecture Center (Microsoft Docs) の Performance antipatternsというドキュメントに、HttpClientのインスタンス化に関する内容が記述されていました。 

参考になりそうですので、一読してみてください。

以下、私の理解ですが(機械翻訳で確認したため、誤読・勘違い等あるかもしれませんが)

  • ASP.NETのApiControllerのサンプルコードを例に、HttpClientをその都度インスタンス化することをアンチパターンとしていて、
  • その解決方法としてHttpClientをStaticで保持することを推奨しています。

cs

1public class SingleHttpClientInstanceController : ApiController 2{ 3 private static readonly HttpClient HttpClient; 4 5 static SingleHttpClientInstanceController() 6 { 7 HttpClient = new HttpClient(); 8 } 9 10 // This method uses the shared instance of HttpClient for every call to GetProductAsync. 11 public async Task<Product> GetProductAsync(string id) 12 { 13 var hostName = HttpContext.Current.Request.Url.Host; 14 var result = await HttpClient.GetStringAsync(string.Format("http://{0}:8080/api/...", hostName)); 15 return new Product { Name = result }; 16 } 17}

また、New Relic APMを利用した負荷テストの結果も記載さてていて、これを見る限りでは(staticとして)多数のユーザーから同時アクセスされた場合でも問題なさそうです。

投稿2017/10/16 14:08

pierre_3

総合スコア60

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/10/16 15:18 編集

readonly を付与するのがポイントでしょうか? それでスレッドセーフになることが保証できて、機能上も問題ないということであれば、static にしても問題なさそうですね。
退会済みユーザー

退会済みユーザー

2017/10/17 02:27 編集

上ののコメントで、 > readonly を付与するのがポイントでしょうか? それでスレッドセーフになることが保証できて・・・ と書きましたけど、考えてみるとそんなことはなさそうですね。 質問者さんが参考にされている "You're using HttpClient wrong and it is destabilizing your software" を見ると、static にした場合 tcp セッションは一本しか張られないように見えますが、だとすると、複数のスレッドが一本の tcp セッションで同時に要求を出したとき、帰ってくる応答を元々要求を出したスレッド別にどうやって識別して渡せるのだろう・・・というところが不思議です。
yamaguri

2017/10/17 02:31

回答ありがとうございます。 公式からたどれる所にある文書であれば、信用しても良さそうです。
guest

0

私はasp.net内でHttpClientをstaticに保持して使用していますが、少なくとも.NET Framework 4.6では特に排他を意識しなくても問題は出ていません。(Application_End等でDisposeはしていますが)
一応下記のようにして大量にリクエストを出す処理を書いてみましたが、とりあえず目立った例外は出ませんでした。

ただし、インスタンスを使い回すとなると、メッセージハンドラが途中で変更できないので、プロキシ設定やクライアント認証が必要な場合は初期化の時点で行う必要がある点等は注意する必要があります。

csharp

1 static Task ClientTask(HttpClient client, string url, int pararellNum, int loopNum, CancellationTokenSource token) 2 { 3 // 並列でHTTPリクエストを出す 4 return Task.WhenAll(Enumerable.Range(0, pararellNum).Select(async idx => 5 { 6 // httpサーバーが起動する前にリクエストを出す可能性があるので、少しの間待つ 7 await Task.Delay(100).ConfigureAwait(false); 8 for (int i = 0; i < loopNum; i++) 9 { 10 // レスポンスはDisposeする必要がある 11 using (var res = await client.GetAsync(url).ConfigureAwait(false)) 12 { 13 res.EnsureSuccessStatusCode(); 14 } 15 } 16 }); 17 18 } 19

投稿2017/10/16 04:17

skitoy4321

総合スコア229

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yamaguri

2017/10/17 02:27

回答ありがとうございます。実績・テストコードともに例外が出ていないのであれば、static化しても問題なさそうですね。
guest

0

上のコメント欄での話と追加情報をここ(回答欄)にまとめて書いておきます。

参考にされている前者の記事にリンクが張ってあった、

You're using HttpClient wrong and it is destabilizing your software
https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/

のコード例のように for ループを回して HttpClient の初期化と Dispose を繰り返すようなことをすると、socket が浪費されて確かに問題がありそうですね。自分は知らなかったです。情報をありがとうございます。

ただ、参考にされている記事のコード例のようなことは普通はしないし、Web アプリでは一つの要求を処理する際一度だけ HttpClient を初期化して、それをその一つの要求の中で使いまわすようにすれば、かなり問題は軽減できそうな気がします。

static化しない場合は要求ごとにインスタンスを生成します。ただ、やはり要求の頻度が多ければ同状況になると思いますので、static化したいと考えました。

それはそうなのでしょうが、それでも static にするのは避けた方がよさそうです。

ASP.NET はユーザーからの要求ごとにスレッドプールからスレッドを取得して処置するマルチスレッドアプリですので、単一の HttpClient のインスタンスを異なるユーザーが共有してどういうことになるか・・・質問者さん独自の条件とかいろいろあるでしょうから自分は分かりません。好ましからざる副作用がありそうで、できればそういうことをしないで済む方向を検討した方がよさそうです。

ASP.NET は応答を返した後、応答を生成するためにメモリにロードした .dll 類は全てアンロードするので、その際 socket も解放されそうな気がします。(気がするだけで、確証はないですが)

最初の質問に書いてあった、

スレッドセーフとあるので

というのは MSDN ライブラリに書いてある "この型の public static (Visual Basic では Shared) のメンバーはすべて、スレッド セーフです" のことと理解していますが、それは「スレッドセーフになるような使い方をすれば」という条件付きです。

その点については、MSDN の英文の説明 "Any public static (Shared in Visual Basic) members of this type are thread safe." でググるといろいろヒットするので見てください。例えば下記:

Why static members are called Threadsafe in MSDN?
https://social.msdn.microsoft.com/Forums/vstudio/en-US/2de53f4d-baf2-4b65-9d0f-82508600fc70/why-static-members-are-called-threadsafe-in-msdn?forum=clr

msdn: What is “Thread Safety”?
https://stackoverflow.com/questions/3137931/msdn-what-is-thread-safety

スレッドセーフになるような使い方というと lock するということになるかと思いますが、それは Web アプリでは本末転倒な話になると思います。

なので、質問者さんが書かれた通り、

副作用も分からないので、いったんstatic化しない方向で実装し、様子を見てみます。

という方向で検討するのがよさそうです。

投稿2017/10/16 03:13

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yamaguri

2017/10/17 02:22

まとめ、ありがとうございます。 スレッドセーフと書かれていても条件付き、というのは知りませんでした。 static化しない方向で様子見と書きましたが、他の方の回答からstatic化しても問題なさそうと分かりましたので、そちらで実装したいと思います。
退会済みユーザー

退会済みユーザー

2017/10/17 02:28

結果を教えていただけると幸いです。 pierre_3 さんのレスへのコメントで、 > readonly を付与するのがポイントでしょうか? それでスレッドセーフになることが保証できて・・・ と書きましたけど、考えてみるとそんなことはなさそうです。 質問者さんが参考にされている "You're using HttpClient wrong and it is destabilizing your software" を見ると、static にした場合 tcp セッションは一本しか張られないように見えますが、だとすると、複数のスレッドが一本の tcp セッションで同時に要求を出したとき、帰ってくる応答を元々要求を出したスレッド別にどうやって識別して渡せるのだろう・・・というところが不思議です。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問