web-dev-qa-db-ja.com

シングルトンhttpclientと新しいhttpclientリクエストの作成

Xamarin.FormsモバイルアプリでHttpClientを使用してWebサービスのレイヤーを作成しようとしています。

  1. シングルトンパターンなし
  2. シングルトンパターン付き

in firstアプローチモバイルアプリケーションによって作成された新しいリクエストごとに新しいhttpクライアントオブジェクトを作成しています。

ここに私のコードがあります

  public HttpClient GetConnection()
        {

            HttpClient httpClient = new HttpClient();
            httpClient.BaseAddress = new Uri(baseAddress); 
            httpClient.Timeout = System.TimeSpan.FromMilliseconds(timeout);


            return httpClient;

        }

ポストリクエストコード

 public async Task<TResult> PostAsync<TRequest, TResult>(String url, TRequest requestData)
        {
            HttpClient client = GetConnection();
            String responseData = null;
            if (client != null)
            {

                String serializedObject = await Task.Run(() => JsonConvert.SerializeObject(requestData, _jsonSerializerSettings));
                var jsonContent = new StringContent(serializedObject, System.Text.Encoding.UTF8, "application/json");
                HttpResponseMessage response = await client.PostAsync(new Uri(url, UriKind.Relative), jsonContent);
                responseData = await HandleResponse(response);


                return await Task.Run(() => JsonConvert.DeserializeObject<TResult>(responseData, _jsonSerializerSettings));


            }
            else
            {

                throw new NullReferenceException("NullReferenceException @ PostAsync  httpclient is null WebRequest.cs");

            }

        }

クライアントは次のコードを使用してリクエストを実行します

new LoginService(new WebRequest()).UserLogin(userRequest);

IWebRequestを実装するクラス内

_webRequest.PostAsync<UserRequest,bool>(Constants.USER_LOGIN, userRequest);

in second approach私はここで新しいリクエストごとに同じhttpクライアントオブジェクトを再利用しています、私のシングルトンクラスもスレッドセーフです。

private static readonly Lazy<HttpService> lazy =
        new Lazy<HttpService>(() => new HttpService());

        public static HttpService Instance { get { return lazy.Value; } }



        private HttpClient getConnection()
        {

            client = new HttpClient();
            client.Timeout = System.TimeSpan.FromMilliseconds(timeout);

            //client.MaxResponseContentBufferSize = 500000;
            client.BaseAddress = new Uri(baseAddress);
            return client;
        }

ポストリクエストコード

public Task<HttpResponseMessage> sendData(String url,String jsonData)
        {

            var jsonContent = new StringContent(jsonData, System.Text.Encoding.UTF8, "application/json");

            return getConnection().PostAsync(new Uri(url, UriKind.Relative), jsonContent);
        }

クライアントは次のコードを使用して実行します

HttpService.Instance.sendData(...)

私はRestSharpのような多くのライブラリを介して、最高のものを探索するためだけに調べましたが、それらのほとんどがリクエストごとに新しいオブジェクトを作成していることがわかりました。だから、どのパターンが最適かわからない。

21
Hunt

UpdateHttpClientの単一の静的インスタンスを使用するようです DNSの変更を尊重しない なので、解決策はHttpClientFactoryを使用することです。それに関するMicrosoftのドキュメントについては、 here を参照してください。

HttpClientFactoryを使用するには、Microsoftの依存性注入を使用する必要があります。これはASP.NET Coreプロジェクトのデフォルトですが、他のプロジェクトではMicrosoft.Extensions.HttpおよびMicrosoft.Extensions.DependencyInjection

次に、サービスコンテナを作成するときに、AddHttpClient()を呼び出すだけです。

var services = new ServiceCollection();
services.AddHttpClient()
var serviceProvider = services.BuildServiceProvider();

そして、HttpClientをサービスにインジェクトでき​​ます。また、舞台裏でHttpClientFactoryHttpClientHandlerオブジェクトのプールを維持します-DNSを最新に保ち、 接続プールの枯渇 の問題を防ぎます。


古い答え:

シングルトンは、HttpClientを使用する正しい方法です。詳細については、 this の記事をご覧ください。

Microsoft docs 状態:

HttpClientは、一度インスタンス化され、アプリケーションのライフサイクルを通じて再利用されることを目的としています。リクエストごとにHttpClientクラスをインスタンス化すると、高負荷で使用可能なソケットの数を使い果たします。これにより、SocketExceptionエラーが発生します。以下は、HttpClientを正しく使用した例です。

そして実際、アプリケーションでこれを見つけました。 foreachループで潜在的に数百のAPIリクエストを行うことができるコードがあり、反復ごとにHttpClientでラップされたusingを作成していました。 MongoClientから、データベースへの接続の試行がタイムアウトになったと言って、ニシンのエラーをすぐに取得し始めました。リンクされた記事を読んだ後、HttpClientを破棄した後でも、利用可能なソケットを使い果たしていることがわかりました。

注意すべき唯一のことは、DefaultRequestHeadersBaseAddressなどが、HttpClientが使用されるすべての場所に適用されることです。シングルトンとして、これは潜在的にアプリケーション全体に存在します。アプリケーションに複数のHttpClientインスタンスを作成できますが、作成するたびに新しい接続プールが作成されるため、控えめに作成する必要があることに注意してください。

Hvaughan3が指摘したように、HttpClientが使用するHttpMessageHandlerのインスタンスも変更できないため、これが問題になる場合は、そのハンドラーで別のインスタンスを使用する必要があります。

34
John

HttpClientは再利用されることになっていますが、必ずしもコードを整理するためにシングルトンを使用する必要があるわけではありません。 ここに私の答え を参照してください。以下も引用。


私はパーティーに遅れていますが、このトリッキーなトピックに関する私の学習の旅はここにあります。

1. HttpClientの再利用に関する公式の支持者はどこにありますか?

HttpClientの再利用が意図されている および そうすることが重要 の場合、そのような支持者は、多くの「高度なトピック」に隠されるのではなく、独自のAPIドキュメントに文書化される方が良い、「パフォーマンス(アンチ)パターン」または他のブログ投稿があります。さもなければ、新しい学習者は手遅れになる前にどのようにそれを知ることになっていますか?

現在(2018年5月)、「c#httpclient」をグーグルで検索したときの最初の検索結果は MSDNのこのAPIリファレンスページ を指していますが、その意図はまったく言及されていません。さて、初心者向けのレッスン1は、MSDNヘルプページの見出しの直後にある「その他のバージョン」リンクをクリックすることです。おそらく「現在のバージョン」へのリンクがあります。このHttpClientの場合、最新のドキュメントが表示されます ここにその意図の説明が含まれています

このトピックに慣れていない多くの開発者も正しいドキュメントページを見つけられなかったため、この知識が広く普及していないため、人々はそれを見つけたときに驚きました 後で 、おそらく 難しい方法で

2. usingIDisposableの(誤解?)概念

これは少し話題から外れていますが、指摘する価値はありますが、前述のブログ投稿でHttpClientIDisposableインターフェースがusing (var client = new HttpClient()) {...}パターンを使用する傾向があると非難するのは偶然ではありませんそして問題につながります。

私はそれが暗黙の(誤?)概念に帰着すると信じています: 「IDisposableオブジェクトは短命であると期待されます」

しかし、このスタイルでコードを記述すると、確かに短命のように見えますが:

using (var foo = new SomeDisposableObject())
{
    ...
}

IDisposableの公式ドキュメント は、IDisposableオブジェクトが短命でなければならないことに言及していない。定義上、IDisposableは、管理されていないリソースを解放するためのメカニズムにすぎません。これ以上何もない。その意味で、あなたは最終的に処分をトリガーすることが期待されていますが、それはあなたが短命の方法でそうすることを必要としません。

したがって、実際のオブジェクトのライフサイクル要件に基づいて、いつ廃棄をトリガーするかを適切に選択するのはあなたの仕事です。 IDisposableを長期間使用することを妨げるものは何もありません。

using System;
namespace HelloWorld
{
    class Hello
    {
        static void Main()
        {
            Console.WriteLine("Hello World!");

            using (var client = new HttpClient())
            {
                for (...) { ... }  // A really long loop

                // Or you may even somehow start a daemon here

            }

            // Keep the console window open in debug mode.
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }
    }
}

この新しい理解により、今度は そのブログ投稿 を再訪し、「修正」がHttpClientを一度初期化するが、破棄しないことを明確に認識できるため、そのnetstat出力から確認できます。つまり、接続はESTABLISHED状態のままであり、適切に閉じられていないことを意味します。閉じている場合、その状態は代わりにTIME_WAITになります。実際には、プログラム全体が終了した後に開いている接続を1つだけリークすることは大したことではなく、ブログの投稿者は修正後もパフォーマンスの向上が見られます。それでも、IDisposableを非難し、それを破棄しないことを選択することは、概念的に間違っています。

3. HttpClientを静的プロパティに入れる必要がありますか、それともシングルトンとして入れる必要がありますか?

前のセクションの理解に基づいて、ここでの答えが明らかになると思います:「必ずしもではない」。 HttpClientを再利用し、(理想的には)最終的に破棄する限り、コードの編成方法に大きく依存します。

陽気なことに、 現在の公式文書の備考セクション の例でさえ、厳密に正しいとは言えません。破棄されない静的HttpClientプロパティを含む「GoodController」クラスを定義します。 例のセクションの別の例 に反している:「disposeを呼び出す必要がある...アプリがリソースをリークしないようにする」。

最後に、シングルトンには独自の課題がないわけではありません。

「グローバル変数は良い考えだと思う人は何人いますか?誰もいません。

シングルトンは良いアイデアだと思う人は何人いますか?少し。

何が得られますか?シングルトンはグローバル変数の集まりです。」

-この感動的な講演から引用 "Global State and Singletons"

PS:SqlConnection

これは現在のQ&Aとは無関係ですが、おそらく知っておくと便利です。 SqlConnectionの使用パターンは異なります。 SqlConnectionを再利用する必要はありません 。接続プールをより適切に処理するためです。

違いは、実装アプローチが原因です。各HttpClientインスタンスは、独自の接続プール( here から引用)を使用します。ただし、SqlConnection自体は、 this に従って中央接続プールによって管理されます。

また、HttpClientの場合と同じように、SqlConnectionを破棄する必要があります。

2
RayLuo

他の人が述べたように、ほとんどの場合、HttpClientをシングルトンとして使用する必要がありますが、例外が1つあります。HTTP long pollingテクニックを使用する場合、HttpClientをシングルトンとして使用しないでください。他のリクエストの実行をブロックするからです。 。

長いポーリングリクエストの場合は、HttpClientを個別に作成する必要があります。

0
mkul