Singleton httpclient vs创建新的httpclient请求

Hun*_*unt 23 c# design-patterns httpclient xamarin

我正在尝试使用Xamarin.Forms移动应用程序中的HttpClient为webservice创建图层.

  1. 没有单一模式
  2. 单身模式

一种方法中,我在移动应用程序发出的每个新请求中创建新的http客户端对象.

这是我的代码

  public HttpClient GetConnection()
        {

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


            return httpClient;

        }
Run Code Online (Sandbox Code Playgroud)

发布请求代码

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

            }

        }
Run Code Online (Sandbox Code Playgroud)

客户端将使用以下代码来执行请求

new LoginService(new WebRequest()).UserLogin(userRequest);
Run Code Online (Sandbox Code Playgroud)

实现的内部类 IWebRequest

_webRequest.PostAsync<UserRequest,bool>(Constants.USER_LOGIN, userRequest);
Run Code Online (Sandbox Code Playgroud)

第二种方法中,我在这里重新使用每个新请求中的相同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;
        }
Run Code Online (Sandbox Code Playgroud)

发布请求代码

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);
        }
Run Code Online (Sandbox Code Playgroud)

客户端将使用以下代码来执行

HttpService.Instance.sendData(...)
Run Code Online (Sandbox Code Playgroud)

我已经浏览了许多类似于RestSharp网络的库,只是为了探索最好的,我发现他们中的大多数都是根据请求创建新对象.所以我很困惑哪种模式最适合.

Joh*_*ica 39

更新:似乎使用单个静态实例HttpClient不尊重DNS更改,因此解决方案是使用HttpClientFactory.请参阅此处了解有关它的Microsoft文档.


Singleton是正确的使用方式HttpClientFactory.有关详细信息,请参阅文章.

Microsoft docs声明:

HttpClient旨在实例化一次,并在应用程序的整个生命周期中重复使用.为每个请求实例化一个HttpClient类将耗尽重负载下可用的套接字数量.这将导致SocketException错误.下面是一个正确使用HttpClient的示例.

事实上,我们在申请中发现了这一点.我们的代码可能会在AddHttpClient()循环中产生数百个API请求,并且对于每次迭代,我们创建了一个HttpClient包装在一个HttpClientFactory.我们很快就开始从我们HttpClientHandler说它已经超时试图连接数据库的过程中得到红鲱鱼错误.在阅读链接文章后,我们发现即使在处理完毕后HttpClient,我们也意识到我们正在耗尽可用的插座.

唯一需要注意的是,在使用HttpClient的任何地方应用foreachHttpClient将要应用的东西.作为单身人士,这可能贯穿整个应用程序.您仍然可以using在应用程序中创建多个实例,但请注意,每次执行时,它们都会创建一个新的连接池,因此应该谨慎创建.

正如hvaughan3所指出的,您也无法更改MongoClientHttpClient使用的实例,因此如果这对您很重要,则需要对该处理程序使用单独的实例.

  • 您也无法更改处理程序,这意味着如果您必须处理不同的auth提供程序,则每个都需要一个`HttpClient`实例. (3认同)

Ray*_*Luo 9

虽然HttpClient应该被重用,但这并不一定意味着我们必须使用单例来组织我们的代码。请在此处参考我的回答。下面也引用了。


我参加聚会迟到了,但这是我对这个棘手话题的学习之旅。

1. 重用HttpClient的官方倡导者在哪里?

我的意思是,如果打算重用 HttpClient 并且这样做很重要,那么这种倡导者最好在其自己的 API 文档中进行记录,而不是隐藏在许多“高级主题”、“性能(反)模式”或其他博客文章中. 否则一个新的学习者如何在为时已晚之前知道它?

截至目前(2018 年 5 月),谷歌搜索“c# httpclient”时的第一个搜索结果指向MSDN 上的此 API 参考页面,但根本没有提及该意图。好吧,对于新手来说,这里的第 1 课是,始终单击 MSDN 帮助页面标题后面的“其他版本”链接,您可能会在那里找到“当前版本”的链接。在这个 HttpClient 案例中,它会将您带到包含该意图描述的最新文档 。

我怀疑许多刚接触这个主题的开发人员也没有找到正确的文档页面,这就是为什么这些知识没有广泛传播的原因,人们后来发现它时感到惊讶 ,可能很难

2. 的(错误?)概念 using IDisposable

这个有点跑题,但还是值得指出的是,在前面提到的那些博客文章中看到人们指责HttpClientIDisposable界面如何使他们倾向于使用该using (var client = new HttpClient()) {...}模式并导致问题,这并非巧合。

我相信这归结为一个不言而喻的(错误?)概念: “一个 IDisposable 对象预计是短暂的”

然而,虽然当我们以这种风格编写代码时,它看起来确实是一件短暂的事情:

using (var foo = new SomeDisposableObject())
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

有了这个新的理解,现在我们重温那篇博文,我们可以清楚地注意到“修复”初始化HttpClient一次但从未处理过它,这就是为什么我们可以从它的 netstat 输出中看到,连接保持在 ESTABLISHED 状态,这意味着它已经未正确关闭。如果它被关闭,它的状态将改为 TIME_WAIT。在实践中,在你的整个程序结束后只泄漏一个打开的连接并不是什么大问题,并且博客发布者在修复后仍然看到性能提升;但是,归咎于 IDisposable 并选择不处理它在概念上是不正确的。

3. 是不是一定要把HttpClient放到一个静态属性里,甚至放到一个单例里?

基于上一节的理解,我想这里的答案就很清楚了:“不一定”。这实际上取决于您如何组织代码,只要您重用 HttpClient 并且(理想情况下)最终处置它。

可笑的是,即使当前官方文档备注部分中的示例 也没有完全正确。它定义了一个“GoodController”类,包含一个不会被释放的静态 HttpClient 属性;这违背了示例部分中另一个示例所 强调的内容:“需要调用 dispose ... 所以应用程序不会泄漏资源”。

最后,单身人士并非没有自己的挑战。

“有多少人认为全局变量是个好主意?没有人。

有多少人认为单身是个好主意?一些。

是什么赋予了?单例只是一堆全局变量。”

-- 引自这个鼓舞人心的演讲,“全局状态和单身人士”

PS:SqlConnection

这个与当前的问答无关,但它可能是一个很好的了解。SqlConnection 使用模式是不同的。您不需要重用 SqlConnection,因为它会以这种方式更好地处理其连接池。

差异是由它们的实现方法引起的。每个 HttpClient 实例使用自己的连接池(引自 此处);但是 SqlConnection 本身是由一个中央连接池管理的,根据这个

并且您仍然需要处理 SqlConnection,就像您应该为 HttpClient 做的那样。


Don*_*a4e 9

.NET 核心 2.1+

何时可以使用 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();
            ...
        }
    }
Run Code Online (Sandbox Code Playgroud)

当您无法使用 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);
        }
        
        ...
    }
Run Code Online (Sandbox Code Playgroud)

参考https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests#alternatives-to-ihttpclientfactory