简体   繁体   English

单例 httpclient 与创建新的 httpclient 请求

[英]Singleton httpclient vs creating new httpclient request

I am trying to create layer for webservice using HttpClient in my Xamarin.Forms mobile app.我正在尝试在我的Xamarin.Forms移动应用程序中使用 HttpClient 为 web 服务创建层。

  1. without singlton pattern没有单例模式
  2. with singleton pattern单例模式

in first approach i am creating new http client object in each new request made by mobile applicaiton.一种方法中,我在移动应用程序发出的每个新请求中创建新的 http 客户端对象。

here is my code这是我的代码

  public HttpClient GetConnection()
        {

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


            return httpClient;

        }

post request code发布请求代码

 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");

            }

        }

client will use following code to execute request客户端将使用以下代码来执行请求

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

inside class that implements IWebRequest内部实现IWebRequest

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

in second approach i am reusing the same http client object in each new request here , my singleton class is thread safe too.第二种方法中,我在这里的每个新请求中重用相同的 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;
        }

post request code发布请求代码

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);
        }

client will use following code to execute客户端将使用以下代码执行

HttpService.Instance.sendData(...)

i have gone through many libraries like RestSharp over web just to explore the best and i found that most of them are creating new objects per request.我已经通过网络浏览了许多像RestSharp这样的RestSharp ,只是为了探索最好的,我发现它们中的大多数都在为每个请求创建新对象。 so i am confused which pattern fits best.所以我很困惑哪种模式最适合。

Update : It seems that using a single static instance of HttpClient doesn't respect DNS changes , so the solution is to use HttpClientFactory .更新:似乎使用HttpClient的单个静态实例不尊重 DNS 更改,因此解决方案是使用HttpClientFactory See here for Microsoft docs about it.有关它的 Microsoft 文档,请参见此处

To use the HttpClientFactory you have to use Microsoft's dependency injection.要使用HttpClientFactory您必须使用 Microsoft 的依赖项注入。 This is the default for ASP.NET Core projects, but for others you will have to reference Microsoft.Extensions.Http and Microsoft.Extensions.DependencyInjection .这是 ASP.NET Core 项目的默认设置,但对于其他项目,您必须引用Microsoft.Extensions.HttpMicrosoft.Extensions.DependencyInjection

Then when you're creating your service container, you simply call AddHttpClient() :然后在创建服务容器时,只需调用AddHttpClient()

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

And then you can inject HttpClient into your services, and behind the scenes HttpClientFactory will maintain a pool of HttpClientHandler objects - keeping your DNS fresh and preventing problems with connection pool exhaustion .然后你可以将HttpClient注入你的服务,在幕后HttpClientFactory将维护一个HttpClientHandler对象池 - 保持你的 DNS 新鲜并防止 连接池耗尽问题。


Old answer:旧答案:

Singleton is the correct way to use HttpClient . Singleton 是使用HttpClient的正确方法。 Please see this article for full details.请参阅 这篇文章以获取完整详细信息。

Microsoft docs state:微软文档状态:

HttpClient is intended to be instantiated once and re-used throughout the life of an application. HttpClient 旨在实例化一次并在应用程序的整个生命周期中重复使用。 Instantiating an HttpClient class for every request will exhaust the number of sockets available under heavy loads.为每个请求实例化一个 HttpClient 类将耗尽重负载下可用的套接字数量。 This will result in SocketException errors.这将导致 SocketException 错误。 Below is an example using HttpClient correctly.下面是一个正确使用 HttpClient 的例子。

And indeed, we found this in our application.事实上,我们在我们的应用程序中发现了这一点。 We have code that can potentially make hundreds of API requests in a foreach loop, and for each iteration we were creating an HttpClient wrapped in a using .我们的代码可能会在foreach循环中发出数百个 API 请求,并且对于每次迭代,我们都创建了一个包装在usingHttpClient We soon started getting red herring errors from our MongoClient saying that it had timed out trying to connect to the database.我们很快就开始从MongoClient收到红鲱鱼错误,说它在尝试连接到数据库时超时。 After reading the linked article, we found that even after disposing of HttpClient , and realised that we were exhausting the available sockets.阅读链接的文章后,我们发现即使在处理HttpClient ,并意识到我们正在耗尽可用的套接字。

The only thing to note is that things like DefaultRequestHeaders and BaseAddress will be applied anywhere that HttpClient is used.唯一需要注意的是DefaultRequestHeadersBaseAddress类的东西将应用于使用 HttpClient 的任何地方。 As a singleton, this is potentially throughout the application.作为单例,这可能贯穿整个应用程序。 You can still create multiple HttpClient instances in your application, but just be aware that each time you do, they create a new connection pool and, as such, should be created sparingly.您仍然可以在应用程序中创建多个HttpClient实例,但请注意,每次这样做时,它们都会创建一个新的连接池,因此应该谨慎创建。

As pointed out by hvaughan3, you also can't change the instance of HttpMessageHandler used by the HttpClient, so if this matters to you, you would need to use a separate instance with that handler.正如 hvaughan3 所指出的,您也不能更改 HttpClient 使用的HttpMessageHandler实例,因此如果这对您很重要,您需要使用带有该处理程序的单独实例。

While HttpClient is supposed to be reused, it does not necessarily mean we have to use singleton to organize our code.虽然HttpClient应该被重用,但这并不一定意味着我们必须使用单例来组织我们的代码。 Please refer to my answer here .在此处参考我的回答 Also quoted below.下面也引用了。


I'm late to the party, but here is my learning journey on this tricky topic.我参加聚会迟到了,但这是我对这个棘手话题的学习之旅。

1. Where can we find the official advocate on reusing HttpClient? 1. 重用HttpClient的官方倡导者在哪里?

I mean, if reusing HttpClient is intended and doing so is important , such advocate is better documented in its own API documentation, rather than being hidden in lots of "Advanced Topics", "Performance (anti)pattern" or other blog posts out there.我的意思是,如果打算重用 HttpClient并且这样做很重要,那么这种倡导者最好在其自己的 API 文档中进行记录,而不是隐藏在许多“高级主题”、“性能(反)模式”或其他博客文章中. Otherwise how is a new learner supposed to know it before it is too late?否则一个新的学习者如何在为时已晚之前知道它?

As of now (May 2018), the first search result when googling "c# httpclient" points to this API reference page on MSDN , which does not mention that intention at all.截至目前(2018 年 5 月),谷歌搜索“c# httpclient”时的第一个搜索结果指向MSDN 上的此 API 参考页面,但根本没有提及该意图。 Well, lesson 1 here for newbie is, always click the "Other Versions" link right after the MSDN help page headline, you will probably find links to the "current version" there.好吧,对于新手来说,这里的第 1 课是,始终单击 MSDN 帮助页面标题后面的“其他版本”链接,您可能会在那里找到“当前版本”的链接。 In this HttpClient case, it will bring you to the latest document here containing that intention description .在这个 HttpClient 案例中,它会将您带到包含该意图描述的最新文档。

I suspect many developers who was new to this topic did not find the correct documentation page either, that's why this knowledge is not widely spread, and people were surprised when they found it out later , possibly in a hard way .我怀疑许多刚接触这个主题的开发人员也没有找到正确的文档页面,这就是为什么这些知识没有广泛传播的原因,人们后来发现它时感到惊讶,可能 很难

2. The (mis?)conception of using IDisposable 2. using IDisposable的(错误?)概念

This one is slightly off-topic but still worth pointing out that, it is not a coincidence to see people in those aforementioned blog posts blaming how HttpClient 's IDisposable interface makes them tend to use the using (var client = new HttpClient()) {...} pattern and then lead to the problem.这个有点偏离主题,但仍然值得指出的是,在上述博客文章中看到人们指责HttpClientIDisposable接口如何使他们倾向于使用using (var client = new HttpClient()) {...}并非巧合using (var client = new HttpClient()) {...}模式,然后导致问题。

I believe that comes down to an unspoken (mis?)conception: "an IDisposable object is expected to be short-lived" .我相信这归结为一个不言而喻的(错误?)概念: “一个 IDisposable 对象预计是短暂的”

HOWEVER, while it certainly looks like a short-lived thing when we write code in this style:然而,虽然当我们以这种风格编写代码时,它看起来确实是一件短暂的事情:

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

the official documentation on IDisposable never mentions IDisposable objects have to be short-lived. IDisposable官方文档从未提到IDisposable对象必须是短暂的。 By definition, IDisposable is merely a mechanism to allow you to release unmanaged resources.根据定义,IDisposable 只是一种允许您释放非托管资源的机制。 Nothing more.而已。 In that sense, you are EXPECTED to eventually trigger the disposal, but it does not require you to do so in a short-lived fashion.从这个意义上说,您预计最终会触发处置,但这并不要求您以短暂的方式这样做。

It is therefore your job to properly choose when to trigger the disposal, base on your real object's life cycle requirement.因此,您的工作是根据真实对象的生命周期要求正确选择触发处置的时间。 There is nothing stopping you from using an IDisposable in a long-lived way:没有什么能阻止您长期使用 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();
        }
    }
}

With this new understanding, now we revisit that blog post , we can clearly notice that the "fix" initializes HttpClient once but never dispose it, that is why we can see from its netstat output that, the connection remains at ESTABLISHED state which means it has NOT been properly closed.有了这个新的理解,现在我们重温那篇 博文,我们可以清楚地注意到“修复”初始化了一次HttpClient但从未处理过它,这就是为什么我们可以从它的 netstat 输出中看到,连接保持在 ESTABLISHED 状态,这意味着它未正确关闭。 If it were closed, its state would be in TIME_WAIT instead.如果它被关闭,它的状态将改为 TIME_WAIT。 In practice, it is not a big deal to leak only one connection open after your entire program ends, and the blog poster still see a performance gain after the fix;在实践中,在你的整个程序结束后只泄漏一个打开的连接并不是什么大问题,并且博主仍然看到修复后的性能提升; but still, it is conceptually incorrect to blame IDisposable and choose to NOT dispose it.但是,归咎于 IDisposable 并选择不处理它在概念上是不正确的。

3. Do we have to put HttpClient into a static property, or even put it as a singleton? 3. 是不是一定要把HttpClient放到一个静态属性里,甚至放到一个单例里?

Based on the understanding of the previous section, I think the answer here becomes clear: "not necessarily".基于上一节的理解,我想这里的答案就很清楚了:“不一定”。 It really depends on how you organize your code, as long as you reuse an HttpClient AND (ideally) dispose it eventually.这实际上取决于您如何组织代码,只要您重用 HttpClient 并且(理想情况下)最终处置它。

Hilariously, not even the example in the Remarks section of the current official document does it strictly right.可笑的是,即使当前官方文档备注部分中的示例也没有完全正确。 It defines a "GoodController" class, containing a static HttpClient property that will not be disposed;它定义了一个“GoodController”类,包含一个不会被释放的静态 HttpClient 属性; which disobeys what another example in the Examples section emphasizes: "need to call dispose ... so app doesn't leak resources".这违背了示例部分中另一个示例所强调的内容:“需要调用 dispose ... 所以应用程序不会泄漏资源”。

And lastly, singleton is not without its own challenges.最后,单身人士并非没有自己的挑战。

"How many people think global variable is a good idea? No one. “有多少人认为全局变量是个好主意?没有人。

How many people think singleton is a good idea?有多少人认为单身是个好主意? A few.一些。

What gives?是什么赋予了? Singletons are just a bunch of global variables."单例只是一堆全局变量。”

-- Quoted from this inspiring talk, "Global State and Singletons" -- 引自这个鼓舞人心的演讲, “全局状态和单身人士”

PS: SqlConnection PS:SqlConnection

This one is irrelevant to the current Q&A, but it is probably a good-to-know.这个与当前的问答无关,但它可能是一个很好的了解。 SqlConnection usage pattern is different. SqlConnection 使用模式是不同的。 You do NOT need to reuse SqlConnection , because it will handle its connection pool better that way.不需要重用 SqlConnection ,因为它会以这种方式更好地处理其连接池。

The difference is caused by their implementation approach.差异是由它们的实现方法引起的。 Each HttpClient instance uses its own connection pool (quoted from here );每个 HttpClient 实例使用自己的连接池(引自此处); but SqlConnection itself is managed by a central connection pool, according to this .但是 SqlConnection 本身是由一个中央连接池管理的,根据这个

And you still need to dispose SqlConnection, same as you are supposed to do for HttpClient.并且您仍然需要处理 SqlConnection,就像您应该为 HttpClient 做的那样。

If you will use HttpClient as static property in WebApi applicaion, you can get following error如果在 WebApi 应用程序中使用 HttpClient 作为静态属性,则会出现以下错误

System.InvalidOperationException: Concurrent reads or writes are not supported.\r\n   at System.IO.Pipelines.PipeCompletion.ThrowLatchedException()\r\n   at System.IO.Pipelines.Pipe.GetReadResult(ReadResult& result)\r\n   at System.IO.Pipelines.Pipe.GetReadAsyncResult()\r\n   at Microsoft.AspNetCore.Server.IIS.Core.IISHttpContext.WriteBody(Boolean flush)","ClassName":"IISHttpContext","MethodName":"WriteBody","EventId":{"Id":3,"Name":"UnexpectedError"},"SourceContext":"Microsoft.AspNetCore.Server.IIS.Core.IISHttpServer"

Error will appear, when inside of you action in webapi controller you are making 2 concurrent requests to same url using HttpClient static instance将出现错误,当您在 webapi 控制器中执行操作时,您使用 HttpClient 静态实例向同一 url 发出 2 个并发请求

therefore i think usage of _httpClientFactory.CreateClient(Guid.NewGuid().ToString()) in action is most safe approach.因此我认为使用_httpClientFactory.CreateClient(Guid.NewGuid().ToString())是最安全的方法。 According to documentation of the method - " It is generally not necessary to dispose of the System.Net.Http.HttpClient as the System.Net.Http.IHttpClientFactory tracks and disposes resources used by the System.Net.Http.HttpClient."根据该方法的文档 - “通常没有必要处理 System.Net.Http.HttpClient,因为 System.Net.Http.IHttpClientFactory 会跟踪和处理 System.Net.Http.HttpClient 使用的资源。”

As others mentioned, mostly HttpClient should be used as singleton, but there is one exception - you should not use HttpClient as singleton when you use HTTP long polling technique, because you will block other requests execution.正如其他人提到的,大部分HttpClient应该用作单例,但有一个例外 - 当您使用HTTP long polling技术时,您不应将HttpClient用作单例,因为您会阻止其他请求的执行。

For long polling requests you should create separate HttpClient .对于长轮询请求,您应该创建单独的HttpClient

.NET Core 2.1+ .NET 核心 2.1+

When you can use DI:何时可以使用 DI:

    using System.Net.Http;

    public class SomeClass
    {
        private readonly IHttpClientFactory _httpClientFactory;

        public SomeClass(IHttpClientFactory httpClientFactory)
        {
            _httpClientFactory = httpClientFactory;
        }

        public void Foo()
        {
            var httpClient = _httpClientFactory.CreateClient();
            ...
        }
    }

When you can't use DI:当您不能使用 DI 时:

    using System.Net.Http;

    public class SomeClass
    {
        private static readonly HttpClient Client;

        static SomeClass()
        {
            var handler = new SocketsHttpHandler
            {
                // Sets how long a connection can be in the pool to be considered reusable (by default - infinite)
                PooledConnectionLifetime = TimeSpan.FromMinutes(1),
            };

            Client = new HttpClient(handler, disposeHandler: false);
        }
        
        ...
    }

Reference https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-5.0#alternatives-to-ihttpclientfactory参考https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-5.0#alternatives-to-ihttpclientfactory

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM