我试图在我的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
,只是为了探索最好的,我发现它们中的大多数都在为每个请求创建新的对象。所以我很困惑哪种模式最合适。
5条答案
按热度按时间ibps3vxo1#
更新:似乎使用
HttpClient
doesn't respect DNS changes的单个静态示例,所以解决方案是使用HttpClientFactory
。有关它的Microsoft文档,请参阅here。要使用
HttpClientFactory
,您必须使用Microsoft的依赖注入。这是ASP.NET Core项目的默认设置,但对于其他项目,您必须引用Microsoft.Extensions.Http和Microsoft.Extensions.DependencyInjection。然后,当你创建服务容器时,你只需调用
AddHttpClient()
:然后,您可以将
IHttpClientFactory
注入到您的服务中,并且在后台HttpClientFactory
将维护HttpClientHandler
对象池-保持DNS更新并防止connection pool exhaustion出现问题。旧答案:
Singleton是使用
HttpClient
的正确方法。详情请参阅this文章。Microsoft docs状态:
HttpClient旨在示例化一次,并在应用程序的整个生命周期中重复使用。为每个请求示例化一个HttpClient类将耗尽重负载下可用的套接字数量。这将导致SocketException错误。下面是一个正确使用HttpClient的例子。
事实上,我们在应用程序中发现了这一点。我们的代码可能会在一个
foreach
循环中发出数百个API请求,对于每个迭代,我们都在创建一个 Package 在using
中的HttpClient
。我们很快就开始从我们的MongoClient
得到红鲱鱼错误,说它已经超时尝试连接到数据库。在阅读了链接的文章之后,我们发现即使在处理了HttpClient
之后,我们也意识到我们正在耗尽可用的套接字。唯一需要注意的是,像
DefaultRequestHeaders
和BaseAddress
这样的东西将应用于任何使用HttpClient的地方。作为单例,这可能贯穿整个应用程序。您仍然可以在应用程序中创建多个HttpClient
示例,但请注意,每次这样做时,它们都会创建一个新的连接池,因此应该谨慎地创建。正如hvaughan 3所指出的,您也不能更改HttpClient使用的
HttpMessageHandler
的示例,因此如果这对您很重要,您需要使用该处理程序的单独示例。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?)
using
IDisposable
的概念这一点有点偏离主题,但仍然值得指出的是,在上述博客文章中看到人们指责
HttpClient
的IDisposable
接口如何使他们倾向于使用using (var client = new HttpClient()) {...}
模式,然后导致问题,这并不是巧合。我相信这归结为一个未说出口的(错误?)构思:“IDisposable对象应该是短期的”。
然而,当我们以这种风格编写代码时,它看起来确实像是一件短命的事情:
official documentation on IDisposable从来没有提到
IDisposable
对象必须是短暂的。根据定义,IDisposable只是一种允许您释放非托管资源的机制。仅此而已从这个意义上说,你被期望最终触发处置,但它并不要求你以短暂的方式这样做。因此,您的工作是根据真实的对象的生命周期要求,正确选择何时触发处理。没有什么可以阻止您以长期的方式使用IDisposable:
有了这个新的理解,现在我们重新访问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做的一样。
3qpi33ja3#
.NET Core 2.1+
当您可以使用DI时:
不能使用DI时:
参考https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests#alternatives-to-ihttpclientfactory
ar5n3qh54#
正如其他人提到的,大多数
HttpClient
应该被用作单例,但有一个例外-当你使用HTTP long polling
技术时,你不应该使用HttpClient
作为单例,因为你会阻止其他请求的执行。对于长轮询请求,您应该创建单独的
HttpClient
。yebdmbv45#
如果你在WebApi应用程序中使用HttpClient作为静态属性,你会得到以下错误
当你在webapi控制器中的操作中使用HttpClient静态示例对同一个url发出2个并发请求时,会出现错误
因此我认为使用
_httpClientFactory.CreateClient(Guid.NewGuid().ToString())
是最安全的方法。根据该方法的文档-“通常不需要释放System.Net.Http.HttpClient,因为System.Net.Http.IHttpClientFactory跟踪并释放System. Net. Http. HttpClient使用的资源。”