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

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

ただいまの
回答率

90.75%

  • C#

    6565questions

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

  • ASP.NET

    481questions

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

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

解決済

回答 3

投稿

  • 評価
  • クリップ 1
  • VIEW 1,354

yamaguri

score 7

前提

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

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

質問内容

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

気になる点

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • SurferOnWww

    2017/10/13 20:19 編集

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

    キャンセル

  • yamaguri

    2017/10/15 00:34

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

    キャンセル

  • SurferOnWww

    2017/10/16 11:25

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

    キャンセル

回答 3

checkベストアンサー

+1

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

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

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

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

    static SingleHttpClientInstanceController()
    {
        HttpClient = new HttpClient();
    }

    // This method uses the shared instance of HttpClient for every call to GetProductAsync.
    public async Task<Product> GetProductAsync(string id)
    {
        var hostName = HttpContext.Current.Request.Url.Host;
        var result = await HttpClient.GetStringAsync(string.Format("http://{0}:8080/api/...", hostName));
        return new Product { Name = result };
    }
}

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/10/17 00:02 編集

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

    キャンセル

  • 2017/10/17 11:26 編集

    上ののコメントで、

    > readonly を付与するのがポイントでしょうか? それでスレッドセーフになることが保証できて・・・

    と書きましたけど、考えてみるとそんなことはなさそうですね。

    質問者さんが参考にされている "You're using HttpClient wrong and it is destabilizing your software" を見ると、static にした場合 tcp セッションは一本しか張られないように見えますが、だとすると、複数のスレッドが一本の tcp セッションで同時に要求を出したとき、帰ってくる応答を元々要求を出したスレッド別にどうやって識別して渡せるのだろう・・・というところが不思議です。

    キャンセル

  • 2017/10/17 11:31

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

    キャンセル

+1

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

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

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

        }

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/10/17 11:27

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

    キャンセル

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/17 11:22

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

    キャンセル

  • 2017/10/17 11:28

    結果を教えていただけると幸いです。

    pierre_3 さんのレスへのコメントで、

    > readonly を付与するのがポイントでしょうか? それでスレッドセーフになることが保証できて・・・

    と書きましたけど、考えてみるとそんなことはなさそうです。

    質問者さんが参考にされている "You're using HttpClient wrong and it is destabilizing your software" を見ると、static にした場合 tcp セッションは一本しか張られないように見えますが、だとすると、複数のスレッドが一本の tcp セッションで同時に要求を出したとき、帰ってくる応答を元々要求を出したスレッド別にどうやって識別して渡せるのだろう・・・というところが不思議です。

    キャンセル

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

  • ただいまの回答率 90.75%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • C#

    6565questions

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

  • ASP.NET

    481questions

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