代码之家  ›  专栏  ›  技术社区  ›  Hunt

单例httpclient与创建新httpclient请求

  •  50
  • Hunt  · 技术社区  · 6 年前

    我正在尝试在我的 Xamarin.Forms 移动应用程序。

    1. 无singlton模式
    2. 使用单例模式

    在里面 第一 方法我在每个新请求中创建新的http客户端对象 通过移动应用程序。

    这是我的密码

      public HttpClient GetConnection()
            {
    
                HttpClient httpClient = new HttpClient();
                httpClient.BaseAddress = new Uri(baseAddress); 
                httpClient.Timeout = System.TimeSpan.FromMilliseconds(timeout);
    
    
                return httpClient;
    
            }
    

    post请求代码

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

    在里面 第二 方法我在每个新请求中重用相同的http客户端对象 在这里,我的singleton类也是线程安全的。

    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请求代码

    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 我发现他们中的大多数人都在根据请求创建新对象。所以我不知道哪种模式最合适。

    5 回复  |  直到 6 年前
        1
  •  82
  •   ProgrammingLlama Raveena Sarda    2 年前

    使现代化 :似乎使用 HttpClient doesn't respect DNS changes ,因此解决方案是使用 HttpClientFactory . 看见 here 关于它的Microsoft文档。

    要使用 HttpClientFactory 您必须使用Microsoft的依赖项注入。这是ASP的默认设置。NET核心项目,但对于其他项目,您必须参考 微软扩展。Http协议 微软扩展。依赖注入 .

    然后,当您创建服务容器时,只需调用 AddHttpClient() :

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

    然后你可以注射 IHttpClientFactory 为您服务,并在幕后 HttpClientFactory 将维持一个 HttpClientHandler 对象-保持DNS新鲜并防止出现问题 connection pool exhaustion .


    旧答案:

    Singleton是正确的使用方法 HttpClient . 请参见 this 文章了解全部详细信息。

    微软 docs 国家:

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

    事实上,我们在应用程序中发现了这一点。我们的代码可以在一个 foreach 循环,并为每个迭代创建 HttpClient 包裹在 using . 我们很快就开始从 MongoClient 表示尝试连接到数据库时超时。阅读链接文章后,我们发现即使在处理 HttpClient ,并意识到我们正在耗尽可用的插座。

    唯一需要注意的是 DefaultRequestHeaders BaseAddress 将应用于使用HttpClient的任何地方。作为一个单例,这可能贯穿整个应用程序。您仍然可以创建多个 HttpClient 实例,但请注意,每次这样做时,它们都会创建一个新的连接池,因此应该谨慎创建。

    正如hvaughan3所指出的,您也不能更改 HttpMessageHandler 由HttpClient使用,因此如果这对您很重要,您需要对该处理程序使用单独的实例。

        2
  •  12
  •   RayLuo    5 年前

    虽然 HttpClient 是应该重用的,但这并不一定意味着我们必须使用单例来组织代码。请参阅 my answer here . 下面也引用了。


    我来晚了,但这是我关于这个棘手话题的学习之旅。

    1、我们在哪里可以找到重用HttpClient的官方倡导者?

    我是说,如果 reusing HttpClient is intended doing so is important , 此类倡导者在其自己的API文档中有更好的文档记录, 而不是隐藏在许多“高级主题”、“性能(反)模式”中 或者其他的博客帖子。 否则,一个新的学习者怎么能在为时已晚之前知道呢?

    截至目前(2018年5月),谷歌搜索“c#httpclient”时的第一个搜索结果 指向 this API reference page on MSDN ,但根本没有提到这一意图。 嗯,新手的第1课是, 始终单击MSDN帮助页标题后的“其他版本”链接, 您可能会在那里找到指向“当前版本”的链接。 在这个HttpClient案例中,它将为您带来最新的文档 here containing that intention description .

    我怀疑许多对这个主题不熟悉的开发人员 也没有找到正确的文档页, 这就是为什么这些知识没有被广泛传播, 人们发现后都很惊讶 later , 可能地 in a hard way .

    (mis?)概念 using IDisposable

    这一点有点离题,但仍然值得指出的是,见到人并非巧合 在上面提到的博客帖子中指责 HttpClient 可识别 界面 使他们倾向于使用 using (var client = new HttpClient()) {...} 图案 然后引出问题。

    我相信这可以归结为一个不言而喻的(错误?)概念: "an IDisposable object is expected to be short-lived" .

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

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

    这个 official documentation on 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();
            }
        }
    }
    

    有了这一新的认识,现在我们重新审视 that blog post , 我们可以清楚地注意到“fix”初始化 HttpClient 一次,但绝不处理, 这就是为什么我们可以从其netstat输出中看到, 连接保持在已建立状态,这意味着它尚未正确关闭。 如果它已关闭,其状态将改为in TIME\u WAIT。 实际上,在整个程序结束后只泄漏一个打开的连接并不是什么大问题, 而博客海报在修复后仍能看到性能提升; 但是,从概念上讲,指责IDisposable而选择不处理它是不正确的。

    3、我们是否必须将HttpClient放入静态属性中,或者甚至将其作为单例?

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

    有趣的是,甚至不是 Remarks section of the current official document 这完全正确。它定义了一个“GoodController”类, 包含不会被处置的静态HttpClient属性; 这违背了什么 another example in the Examples section 强调:“需要调用dispose…以便应用程序不会泄漏资源”。

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

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

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

    有什么好处?单例只是一组全局变量。"

    --引自这篇鼓舞人心的演讲, "Global State and Singletons"

    PS:SqlConnection

    这与当前的Q&无关;A、 但这可能是个好消息。 SqlConnection使用模式不同。 你 do NOT need to reuse SqlConnection , 因为这样可以更好地处理连接池。

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

    您仍然需要处理SqlConnection,就像您应该为HttpClient所做的一样。

        3
  •  2
  •   DonSleza4e    3 年前

    .净核心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();
                ...
            }
        }
    

    当您无法使用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);
            }
            
            ...
        }
    

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

        4
  •  1
  •   Sergey Surnin    3 年前

    如果将HttpClient用作WebApi应用程序中的静态属性,则可能会出现以下错误

    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"
    

    当您在webapi控制器中执行操作时,将出现错误,您正在使用HttpClient静态实例对同一url发出2个并发请求

    因此我认为 _httpClientFactory.CreateClient(Guid.NewGuid().ToString()) 实际操作是最安全的方法。根据方法文件- “由于System.Net.Http.IHttpClientFactory跟踪和处理System.Net.Http.HttpClient使用的资源,通常不需要处理System.Net.Http.HttpClient。”

        5
  •  0
  •   mkul    6 年前

    正如其他人提到的,主要是 HttpClient 应作为单例使用,但有一个例外-不应使用 HttpClient 当您使用 HTTP long polling 技术,因为您将阻止其他请求的执行。

    对于长轮询请求,您应该创建单独的 HttpClient .