Hun*_*unt 23 c# design-patterns httpclient xamarin
我正在尝试使用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;
}
Run Code Online (Sandbox Code Playgroud)
发布请求代码
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");
}
}
Run Code Online (Sandbox Code Playgroud)
客户端将使用以下代码来执行请求
new LoginService(new WebRequest()).UserLogin(userRequest);
Run Code Online (Sandbox Code Playgroud)
实现的内部类 IWebRequest
_webRequest.PostAsync<UserRequest,bool>(Constants.USER_LOGIN, userRequest);
Run Code Online (Sandbox Code Playgroud)
在第二种方法中,我在这里重新使用每个新请求中的相同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;
}
Run Code Online (Sandbox Code Playgroud)
发布请求代码
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);
}
Run Code Online (Sandbox Code Playgroud)
客户端将使用以下代码来执行
HttpService.Instance.sendData(...)
Run Code Online (Sandbox Code Playgroud)
我已经浏览了许多类似于RestSharp网络的库,只是为了探索最好的,我发现他们中的大多数都是根据请求创建新对象.所以我很困惑哪种模式最适合.
Joh*_*ica 39
更新:似乎使用单个静态实例HttpClient不尊重DNS更改,因此解决方案是使用HttpClientFactory.请参阅此处了解有关它的Microsoft文档.
Singleton是正确的使用方式HttpClientFactory.有关详细信息,请参阅此文章.
Microsoft docs声明:
HttpClient旨在实例化一次,并在应用程序的整个生命周期中重复使用.为每个请求实例化一个HttpClient类将耗尽重负载下可用的套接字数量.这将导致SocketException错误.下面是一个正确使用HttpClient的示例.
事实上,我们在申请中发现了这一点.我们的代码可能会在AddHttpClient()循环中产生数百个API请求,并且对于每次迭代,我们创建了一个HttpClient包装在一个HttpClientFactory.我们很快就开始从我们HttpClientHandler说它已经超时试图连接数据库的过程中得到红鲱鱼错误.在阅读链接文章后,我们发现即使在处理完毕后HttpClient,我们也意识到我们正在耗尽可用的插座.
唯一需要注意的是,在使用HttpClient的任何地方应用foreach和HttpClient将要应用的东西.作为单身人士,这可能贯穿整个应用程序.您仍然可以using在应用程序中创建多个实例,但请注意,每次执行时,它们都会创建一个新的连接池,因此应该谨慎创建.
正如hvaughan3所指出的,您也无法更改MongoClientHttpClient使用的实例,因此如果这对您很重要,则需要对该处理程序使用单独的实例.
虽然HttpClient应该被重用,但这并不一定意味着我们必须使用单例来组织我们的代码。请在此处参考我的回答。下面也引用了。
我参加聚会迟到了,但这是我对这个棘手话题的学习之旅。
我的意思是,如果打算重用 HttpClient 并且这样做很重要,那么这种倡导者最好在其自己的 API 文档中进行记录,而不是隐藏在许多“高级主题”、“性能(反)模式”或其他博客文章中. 否则一个新的学习者如何在为时已晚之前知道它?
截至目前(2018 年 5 月),谷歌搜索“c# httpclient”时的第一个搜索结果指向MSDN 上的此 API 参考页面,但根本没有提及该意图。好吧,对于新手来说,这里的第 1 课是,始终单击 MSDN 帮助页面标题后面的“其他版本”链接,您可能会在那里找到“当前版本”的链接。在这个 HttpClient 案例中,它会将您带到包含该意图描述的最新文档 。
我怀疑许多刚接触这个主题的开发人员也没有找到正确的文档页面,这就是为什么这些知识没有广泛传播的原因,人们后来发现它时感到惊讶 ,可能很难。
using IDisposable这个有点跑题,但还是值得指出的是,在前面提到的那些博客文章中看到人们指责HttpClient的IDisposable界面如何使他们倾向于使用该using (var client = new HttpClient()) {...}模式并导致问题,这并非巧合。
我相信这归结为一个不言而喻的(错误?)概念: “一个 IDisposable 对象预计是短暂的”。
然而,虽然当我们以这种风格编写代码时,它看起来确实是一件短暂的事情:
using (var foo = new SomeDisposableObject())
{
...
}
Run Code Online (Sandbox Code Playgroud)
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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
有了这个新的理解,现在我们重温那篇博文,我们可以清楚地注意到“修复”初始化HttpClient一次但从未处理过它,这就是为什么我们可以从它的 netstat 输出中看到,连接保持在 ESTABLISHED 状态,这意味着它已经未正确关闭。如果它被关闭,它的状态将改为 TIME_WAIT。在实践中,在你的整个程序结束后只泄漏一个打开的连接并不是什么大问题,并且博客发布者在修复后仍然看到性能提升;但是,归咎于 IDisposable 并选择不处理它在概念上是不正确的。
基于上一节的理解,我想这里的答案就很清楚了:“不一定”。这实际上取决于您如何组织代码,只要您重用 HttpClient 并且(理想情况下)最终处置它。
可笑的是,即使当前官方文档的备注部分中的示例 也没有完全正确。它定义了一个“GoodController”类,包含一个不会被释放的静态 HttpClient 属性;这违背了示例部分中另一个示例所 强调的内容:“需要调用 dispose ... 所以应用程序不会泄漏资源”。
最后,单身人士并非没有自己的挑战。
“有多少人认为全局变量是个好主意?没有人。
有多少人认为单身是个好主意?一些。
是什么赋予了?单例只是一堆全局变量。”
-- 引自这个鼓舞人心的演讲,“全局状态和单身人士”
这个与当前的问答无关,但它可能是一个很好的了解。SqlConnection 使用模式是不同的。您不需要重用 SqlConnection,因为它会以这种方式更好地处理其连接池。
差异是由它们的实现方法引起的。每个 HttpClient 实例使用自己的连接池(引自 此处);但是 SqlConnection 本身是由一个中央连接池管理的,根据这个。
并且您仍然需要处理 SqlConnection,就像您应该为 HttpClient 做的那样。
using System.Net.Http;
public class SomeClass
{
private readonly IHttpClientFactory _httpClientFactory;
public SomeClass(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public void Foo()
{
var httpClient = _httpClientFactory.CreateClient();
...
}
}
Run Code Online (Sandbox Code Playgroud)
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);
}
...
}
Run Code Online (Sandbox Code Playgroud)