我正在尝试使用我的Xamarin.Forms
移动应用中的HttpClient为webservice创建图层。
在第一个方法中,我在每个新请求中创建新的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
这样的库只是为了探索最好的,我发现他们中的大多数都在为每个请求创建新对象。所以我很困惑哪种模式最合适。
答案 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
之后,我们也意识到我们正在耗尽可用的套接字。
唯一需要注意的是,DefaultRequestHeaders
和BaseAddress
之类的内容将应用于使用HttpClient的任何位置。作为单身人士,这可能贯穿整个应用程序。您仍然可以在应用程序中创建多个HttpClient
实例,但请注意,每次执行时,都会创建一个新的连接池,因此应该谨慎创建。
正如hvaughan3所指出的那样,你也无法改变HttpClient使用的HttpMessageHandler
实例,所以如果这对你很重要,你需要在该处理程序中使用一个单独的实例。
答案 1 :(得分:1)
虽然HttpClient
应该被重用,但这并不一定意味着我们必须使用单例来组织我们的代码。请参阅my answer here。也引用如下。
我迟到了,但这是我在这个棘手话题上的学习之旅。
我的意思是,如果reusing HttpClient is intended 和doing 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。
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并且选择不处理它在概念上是不正确的。
基于对前一节的理解, 我认为这里的答案变得清晰:“不一定”。 这实际上取决于你如何组织代码, 只要你重用一个HttpClient AND(理想情况下)最终处理它。
非常热闹,甚至没有例子 Remarks section of the current official document 它是完全正确的。它定义了一个“GoodController”类, 包含不会被处理的静态HttpClient属性; 哪个不服从another example in the Examples section 强调:“需要调用dispose ......所以应用程序不会泄漏资源”。
最后,单身人士并非没有自己的挑战。
“有多少人认为全局变量是一个好主意?没有人。
有多少人认为单身人士是个好主意?一些。
是什么给出的?单身人士只是一堆全球性变量。“
- 引用这个鼓舞人心的演讲,"Global State and Singletons"
这个与当前的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)
using System.Net.Http;
public class SomeClass
{
private readonly IHttpClientFactory _httpClientFactory;
public SomeClass(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public void Foo()
{
var httpClient = _httpClientFactory.CreateClient();
...
}
}
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);
}
...
}