Singleton httpclient与创建新的httpclient请求

时间:2018-02-14 02:03:46

标签: c# xamarin design-patterns httpclient

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

  1. 没有单一模式
  2. 单身模式
  3. 第一个方法中,我在每个新请求中创建新的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);
    

    第二方法中,我在每个新请求中重复使用相同的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这样的库只是为了探索最好的,我发现他们中的大多数都在为每个请求创建新对象。所以我很困惑哪种模式最合适。

5 个答案:

答案 0 :(得分:27)

更新:似乎使用HttpClient doesn't respect DNS changes的单个静态实例,因此解决方案是使用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注入到您的服务中,在幕后HttpClientFactory将维护一个HttpClientHandler个对象池 - 保持您的DNS新鲜并防止{{3}出现问题}。

旧回答:

Singleton是使用HttpClient的正确方法。有关详细信息,请参阅connection pool exhaustion文章。

Microsoft this声明:

  

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

事实上,我们在申请中发现了这一点。我们的代码可能会在foreach循环中产生数百个API请求,并且对于每次迭代,我们创建了一个HttpClient包裹在using中。我们很快就开始从我们的MongoClient获得红色鲱鱼错误,说它已经超时尝试连接到数据库。在阅读链接文章后,我们发现即使在处理HttpClient之后,我们也意识到我们正在耗尽可用的套接字。

唯一需要注意的是,DefaultRequestHeadersBaseAddress之类的内容将应用于使用HttpClient的任何位置。作为单身人士,这可能贯穿整个应用程序。您仍然可以在应用程序中创建多个HttpClient实例,但请注意,每次执行时,都会创建一个新的连接池,因此应该谨慎创建。

正如hvaughan3所指出的那样,你也无法改变HttpClient使用的HttpMessageHandler实例,所以如果这对你很重要,你需要在该处理程序中使用一个单独的实例。

答案 1 :(得分:1)

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

我迟到了,但这是我在这个棘手话题上的学习之旅。

1。我们在哪里可以找到重用HttpClient的官方拥护者?

我的意思是,如果reusing HttpClient is intendeddoing so is important, 这样的倡导者在其自己的API文档中有更好的记录, 而不是隐藏在许多“高级主题”,“性能(反)模式” 或其他博客文章。 否则一个新学习者在为时已晚之前应该如何知道呢?

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

我怀疑很多开发人员对这个话题不熟悉 找不到正确的文档页面, 这就是为什么这种知识没有广泛传播, 当他们发现它时,人们感到惊讶 later, 可能in a hard way

2。 ({1}} using

的(错误?)概念

这个稍微偏离主题,但仍然值得指出,看到人们并不是巧合 在上述博客文章中指责IDisposable的{​​{1}}界面 使他们倾向于使用HttpClient模式 然后导致问题。

我认为这归结为一个未说出口的(错误?)概念: "an IDisposable object is expected to be short-lived"

但是,当我们用这种风格编写代码时,它看起来确实是一个短命的东西:

IDisposable

official documentation on IDisposable 永远不会提到using (var client = new HttpClient()) {...}对象必须是短暂的。 根据定义,IDisposable只是一种允许您释放非托管资源的机制。 而已。从这个意义上说,你预计会最终触发处置, 但它并不要求你以短暂的方式这样做。

因此,您的工作是正确选择何时触发处置, 基于您的真实对象的生命周期要求。 没有什么可以阻止你以长期的方式使用IDisposable:

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

有了这种新的理解,现在我们重新审视that blog post, 我们可以清楚地注意到“修复”初始化IDisposable一次但从不处理它, 这就是为什么我们可以从它的netstat输出看到, 连接仍处于ESTABLISHED状态,这意味着它尚未正确关闭。 如果它被关闭,它的状态将改为TIME_WAIT。 实际上,在整个程序结束后只打开一个连接并不是什么大不了的事, 并且博客海报仍然看到修复后的性能提升; 但是,归咎于IDisposable并且选择不处理它在概念上是不正确的。

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

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

非常热闹,甚至没有例子 Remarks section of the current official document 它是完全正确的。它定义了一个“GoodController”类, 包含不会被处理的静态HttpClient属性; 哪个不服从another example in the Examples section 强调:“需要调用dispose ......所以应用程序不会泄漏资源”。

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

  

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

     

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

     

是什么给出的?单身人士只是一堆全球性变量。“

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

PS:SqlConnection

这个与当前的Q&amp; A无关,但它可能是一个很好的知识。 SqlConnection的使用模式不同。 你do NOT need to reuse SqlConnection, 因为它会更好地处理它的连接池。

差异是由他们的实施方法引起的。 每个HttpClient实例都使用自己的连接池(引自 here); 但是SqlConnection本身由中央连接池管理, 根据{{​​3}}。

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

答案 2 :(得分:1)

如果你在 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"

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

因此我认为使用 _httpClientFactory.CreateClient(Guid.NewGuid().ToString()) in action 是最安全的方法。根据该方法的文档 - " 一般不需要处理 System.Net.Http.HttpClient,因为 System.Net.Http.IHttpClientFactory 会跟踪和处理 System.Net.Http.HttpClient 使用的资源。"

答案 3 :(得分:0)

正如其他人所提到的,多数情况下HttpClient应该用作单例,但是有一个例外-使用HttpClient技术时,您不应该将HTTP long polling用作单例,因为您将阻塞其他请求执行。

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

答案 4 :(得分:0)

.NET Core 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