xamarin Singleton httpclient vs创建新的httpclient请求

thtygnil  于 10个月前  发布在  其他
关注(0)|答案(5)|浏览(85)

我试图在我的Xamarin.Forms移动的应用程序中使用HttpClient为Web服务创建层。
1.无单振子模式
1.单例模式
在第一种方法中,我在移动的应用程序发出的每个新请求中创建新的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(...)

我已经通过Web浏览了许多库,如RestSharp,只是为了探索最好的,我发现它们中的大多数都在为每个请求创建新的对象。所以我很困惑哪种模式最合适。

ibps3vxo

ibps3vxo1#

更新:似乎使用HttpClientdoesn't respect DNS changes的单个静态示例,所以解决方案是使用HttpClientFactory。有关它的Microsoft文档,请参阅here

要使用HttpClientFactory,您必须使用Microsoft的依赖注入。这是ASP.NET Core项目的默认设置,但对于其他项目,您必须引用Microsoft.Extensions.HttpMicrosoft.Extensions.DependencyInjection
然后,当你创建服务容器时,你只需调用AddHttpClient()

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

然后,您可以将IHttpClientFactory注入到您的服务中,并且在后台HttpClientFactory将维护HttpClientHandler对象池-保持DNS更新并防止connection pool exhaustion出现问题。

旧答案:

Singleton是使用HttpClient的正确方法。详情请参阅this文章。
Microsoft docs状态:
HttpClient旨在示例化一次,并在应用程序的整个生命周期中重复使用。为每个请求示例化一个HttpClient类将耗尽重负载下可用的套接字数量。这将导致SocketException错误。下面是一个正确使用HttpClient的例子。
事实上,我们在应用程序中发现了这一点。我们的代码可能会在一个foreach循环中发出数百个API请求,对于每个迭代,我们都在创建一个 Package 在using中的HttpClient。我们很快就开始从我们的MongoClient得到红鲱鱼错误,说它已经超时尝试连接到数据库。在阅读了链接的文章之后,我们发现即使在处理了HttpClient之后,我们也意识到我们正在耗尽可用的套接字。
唯一需要注意的是,像DefaultRequestHeadersBaseAddress这样的东西将应用于任何使用HttpClient的地方。作为单例,这可能贯穿整个应用程序。您仍然可以在应用程序中创建多个HttpClient示例,但请注意,每次这样做时,它们都会创建一个新的连接池,因此应该谨慎地创建。
正如hvaughan 3所指出的,您也不能更改HttpClient使用的HttpMessageHandler的示例,因此如果这对您很重要,您需要使用该处理程序的单独示例。

8oomwypt

8oomwypt2#

虽然HttpClient应该被重用,但这并不一定意味着我们必须使用单例来组织代码。请参阅my answer here。也在下面引用。
我迟到了,但这是我对这个棘手主题的学习之旅。

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

我的意思是,如果打算重用HttpClient和doing so is important,这样的倡导者最好在自己的API文档中记录,而不是隐藏在大量的“高级主题”,“性能(反)模式”或其他博客文章中。否则,一个新的学习者如何在为时已晚之前知道它?
截至目前(2018年5月),在谷歌上搜索“c# httpclient”时的第一个搜索结果指向this API reference page on MSDN,它根本没有提到这个意图。好吧,这里给新手的第一课是,总是点击MSDN帮助页面标题后面的“其他版本”链接,你可能会在那里找到指向“当前版本”的链接。在这个HttpClient案例中,它会将您带到包含该意图描述的最新文档。
我怀疑许多新的开发人员也没有找到正确的文档页面,这就是为什么这个知识没有广泛传播,当人们发现later,可能是in a hard way时,他们感到惊讶。

2.(mis?)usingIDisposable的概念

这一点有点偏离主题,但仍然值得指出的是,在上述博客文章中看到人们指责HttpClientIDisposable接口如何使他们倾向于使用using (var client = new HttpClient()) {...}模式,然后导致问题,这并不是巧合。
我相信这归结为一个未说出口的(错误?)构思:“IDisposable对象应该是短期的”。
然而,当我们以这种风格编写代码时,它看起来确实像是一件短命的事情:

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

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

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

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

基于对上一节的理解,我认为这里的答案变得清晰起来:“不一定”。这真的取决于你如何组织你的代码,只要你重用一个HttpClient并(理想情况下)最终处理它。
可笑的是,即使是当前官方文件备注部分的例子也没有严格正确。它定义了一个“GoodController”类,包含一个不会被释放的静态HttpClient属性;这违背了示例部分中另一个示例所强调的:“需要呼叫处置...所以应用程序不会泄漏资源”。
最后,单例并非没有自己的挑战。
“有多少人认为全局变量是个好主意?没人啊
有多少人认为单身是个好主意?几个.
单例只是一堆全局变量。”
--引自这篇鼓舞人心的演讲,"Global State and Singletons"

PS:SqlConnection

这一个与当前的Q&A无关,但它可能是一个很好的知道。SqlConnection使用模式不同。您可以使用do NOT need to reuse SqlConnection,因为这样可以更好地处理连接池。
这种差异是由他们的执行方式造成的。每个HttpClient示例都使用自己的连接池(引用自此处);但是根据这个,SqlConnection本身由一个中央连接池管理。
你仍然需要释放SqlConnection,就像你应该为HttpClient做的一样。

3qpi33ja

3qpi33ja3#

.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://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests#alternatives-to-ihttpclientfactory

ar5n3qh5

ar5n3qh54#

正如其他人提到的,大多数HttpClient应该被用作单例,但有一个例外-当你使用HTTP long polling技术时,你不应该使用HttpClient作为单例,因为你会阻止其他请求的执行。
对于长轮询请求,您应该创建单独的HttpClient

yebdmbv4

yebdmbv45#

如果你在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())是最安全的方法。根据该方法的文档-“通常不需要释放System.Net.Http.HttpClient,因为System.Net.Http.IHttpClientFactory跟踪并释放System. Net. Http. HttpClient使用的资源。”

相关问题