在HttpClient和WebClient之间做出决策
在HttpClient和WebClient之间做出决策
我们的Web应用程序在.NET Framework 4.0中运行。UI通过Ajax调用控制器方法。\n我们需要使用供应商的REST服务。我正在评估在.NET 4.0中调用REST服务的最佳方法。REST服务需要基本身份验证方案,并且可以返回XML和JSON格式的数据。\n没有上传/下载大数据的要求,并且我在将来也没有看到这方面的需求。我看了一些用于REST消费的开源代码项目,但没有找到任何价值来证明在项目中增加依赖的必要性。我开始评估WebClient和HttpClient。我从NuGet下载了.NET 4.0的HttpClient。\n我搜索了WebClient和HttpClient之间的区别,这个网站提到了单个HttpClient可以处理并发调用,并且可以重用解析的DNS、cookie配置和身份验证。我还没有看到由于这些差异可能带来的实际价值。\n我进行了快速性能测试,以了解WebClient(同步调用)、HttpClient(同步和异步调用)的性能。以下是结果:\n我对所有请求(最小-最大)使用相同的HttpClient实例。\n
\nWebClient同步:8毫秒-167毫秒
\nHttpClient同步:3毫秒-7228毫秒
\nHttpClient异步:985毫秒-10405毫秒\n
\n对于每个请求(最小-最大)使用新的HttpClient:\n
\nWebClient同步:4毫秒-297毫秒
\nHttpClient同步:3毫秒-7953毫秒
\nHttpClient异步:1027毫秒-10834毫秒\n
\n
代码
\n
public class AHNData { public int i; public string str; } public class Program { public static HttpClient httpClient = new HttpClient(); private static readonly string _url = "http://localhost:9000/api/values/"; public static void Main(string[] args) { #region "Trace" Trace.Listeners.Clear(); TextWriterTraceListener twtl = new TextWriterTraceListener( "C:\\Temp\\REST_Test.txt"); twtl.Name = "TextLogger"; twtl.TraceOutputOptions = TraceOptions.ThreadId | TraceOptions.DateTime; ConsoleTraceListener ctl = new ConsoleTraceListener(false); ctl.TraceOutputOptions = TraceOptions.DateTime; Trace.Listeners.Add(twtl); Trace.Listeners.Add(ctl); Trace.AutoFlush = true; #endregion int batchSize = 1000; ParallelOptions parallelOptions = new ParallelOptions(); parallelOptions.MaxDegreeOfParallelism = batchSize; ServicePointManager.DefaultConnectionLimit = 1000000; Parallel.For(0, batchSize, parallelOptions, j => { Stopwatch sw1 = Stopwatch.StartNew(); GetDataFromHttpClientAsync>(sw1); }); Parallel.For(0, batchSize, parallelOptions, j => { Stopwatch sw1 = Stopwatch.StartNew(); GetDataFromHttpClientSync
>(sw1); }); Parallel.For(0, batchSize, parallelOptions, j => { using (WebClient client = new WebClient()) { Stopwatch sw = Stopwatch.StartNew(); byte[] arr = client.DownloadData(_url); sw.Stop(); Trace.WriteLine("WebClient同步 " + sw.ElapsedMilliseconds); } }); Console.Read(); } public static T GetDataFromWebClient
() { using (var webClient = new WebClient()) { webClient.BaseAddress = _url; return JsonConvert.DeserializeObject ( webClient.DownloadString(_url)); } } public static void GetDataFromHttpClientSync (Stopwatch sw) { HttpClient httpClient = new HttpClient(); var response = httpClient.GetAsync(_url).Result; var obj = JsonConvert.DeserializeObject ( response.Content.ReadAsStringAsync().Result); sw.Stop(); Trace.WriteLine("HttpClient同步 " + sw.ElapsedMilliseconds); } public static void GetDataFromHttpClientAsync (Stopwatch sw) { HttpClient httpClient = new HttpClient(); var response = httpClient.GetAsync(_url).ContinueWith( (a) => { JsonConvert.DeserializeObject ( a.Result.Content.ReadAsStringAsync().Result); sw.Stop(); Trace.WriteLine("HttpClient异步 " + sw.ElapsedMilliseconds); }, TaskContinuationOptions.None); } } }
\n
我的问题
\n
- \n
- REST调用返回的时间是在3-4秒之内,这是可以接受的。对REST服务的调用是从Ajax调用的控制器方法中发起的。首先,调用在不同的线程中运行,不会阻塞UI。所以,我可以只使用同步调用吗?
- 上述代码在我的本地机器上运行。在生产环境中,会涉及DNS和代理查找。使用HttpClient相比WebClient有什么优势吗?
- HttpClient的并发性是否优于WebClient?从测试结果来看,WebClient的同步调用性能更好。
- 如果升级到.NET 4.5,HttpClient是否是更好的设计选择?性能是关键设计因素。
\n
\n
\n
\n
在ASP.NET应用中,我仍然更喜欢使用WebClient而不是HttpClient,原因如下:
1. 现代实现具有异步/可等待的基于任务的方法。
2. 具有更小的内存占用和2-5倍的速度(其他答案已经提到了这一点)。
3. 据建议,"在应用程序的整个生命周期中重用单个HttpClient实例"。但是ASP.NET没有"应用程序的整个生命周期",只有请求的生命周期。ASP.NET 5的当前指导是使用HttpClientFactory,但它只能通过依赖注入使用。一些人希望有一个更简单的解决方案。
4. 最重要的是,如果像微软建议的那样在应用程序的整个生命周期中使用一个单例实例的HttpClient,它存在已知的问题。例如DNS缓存问题-HttpClient简单地忽略TTL并永久缓存DNS。然而,有解决方法。如果您想了解有关HttpClient的问题和混淆的更多信息,只需阅读Microsoft GitHub上的此评论。
考虑到.NET Core已取代.NET Framework,你是否已经使用.NET Core运行过你的基准测试?现在HttpWebRequest是HttpClient的包装器,所以WebClient本质上是WebClient的传统适配器。
"只有请求的生命周期。"这是错误的。在旧的ASP.NET堆栈中,可以使用DI容器来提供单例或作用域对象,只是使用起来更困难。
是的,但你仍然无法控制应用程序的生命周期。而普通的程序员不会费心创建静态/单例变量来缓存HttpClient。
应用程序的生命周期并不重要,只有注入的HttpClient-或者更准确地说,HttpClientHandler的生命周期。这对于所有应用程序来说都很容易做到。如果你的结果显示包装类比类本身更快或者内存使用更少,那就有问题了。
此外,".NET Core已取代.NET Framework" - 它还没有完全取代,.NET Framework仍然得到支持,并且将在未来10年内得到支持(基本上与其作为Windows的一部分一样长)。但是,我可能应该指出我的答案是针对.NET Framework而不是Core的。
顺便说一句,查看HttpWebRequest的源代码,我看到它创建了一个新的HttpCLientHandler。你能否指出其他的地方?
再往下两行,它在一个HttpClient中使用。这是.NET Old的存储库。在.NET Core中,自2019年8月以来,客户端和处理程序是只读字段,并且自2019年10月以来被缓存。当前的代码是“private static volatile HttpClient?s_cachedHttpClient;”
至于.NET Framework仍然得到支持,它的“支持”方式就像Silverlight在过去的10年中得到的“支持”一样。没有新的开发,只有补丁,而且经过一段时间后,只有在支付费用后才会有补丁。你发布的链接显示了这一点-在过去的3年中没有对HttpWebRequest进行修复。HttpClient在不应该被释放的时候被释放。即使SendRequest是异步的,也没有GetResponseAsync。APM方法只是对SendRequest任务的包装。
这意味着在.NET Framework中使用HttpWebRequest存在套接字耗尽的问题。这展示了“支持”的含义。微软知道这个问题,但没有修复它。如果你付费解决这个问题,你会得到一个只为你定制的二进制文件。
关于.NET Core和Framework的观点是正确的,尽管产品还不成熟(这是一个刚刚移植了一个庞大的应用程序的人的个人意见,可能应该被忽略)。非常感谢提供的源代码链接。我们正在使用dotnet-counters在生产环境中密切监控线程饥饿情况,并且我确认WebClient在.NET Core中不会导致线程饥饿(如果异步使用)。
asp.net 5是什么?你是说.net 5吗?
问题的出现的原因:评估创建HttpClient的不同方式,并了解HttpClientFactory。
解决方法:使用HttpClientFactory来实现弹性的HTTP请求。
文章内容如下:
HttpClientFactory
一个非常重要的问题是评估创建HttpClient的不同方式,其中的一部分就是了解HttpClientFactory。
这里提供了一个链接,可以了解更多关于如何使用HttpClientFactory实现弹性的HTTP请求的信息:https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests
我知道这不是一个直接的答案,但是你最好从这里开始,而不是在各个地方都使用new HttpClient(...)
。
值得强调的是,即使在.NET Framework中使用HttpWebRequest和WebClient,它们至少从2018年开始使用HttpClient,所以这个问题实际上是没有意义的。
在选择HttpClient和WebClient之间做出决策时,出现了以下原因:
1. HttpClient是较新的API,具有以下优点:
- 具有良好的异步编程模型。
- 由HTTP的发明者之一Henrik F Nielson开发,设计API使得您能够遵循HTTP标准,例如生成符合标准的头部。
- 在.NET框架4.5中,因此在可预见的未来有一定程度的支持。
- 还有可复制/可移植的框架版本的库,如果您想在其他平台上使用它,如.NET 4.0、Windows Phone等。
2. 如果您正在编写一个向其他Web服务发起REST调用的Web服务,您应该使用异步编程模型来处理所有的REST调用,这样您就不会遇到线程饥饿的问题。您可能还想使用最新的C#编译器,该编译器支持async/await。
3. 需要注意的是,就性能而言,据我所知,HttpClient与WebClient可能有类似的性能,如果您进行公平的测试。
4. 如果它有切换代理的方法,那就太好了。
鉴于上述原因,微软在.NET 5的文档中明确表示不推荐使用WebClient类进行新项目开发,而建议使用System.Net.Http.HttpClient类。
在选择HttpClient和WebClient之间进行决策时,应考虑到HttpClient的新特性、良好的异步编程模型以及其在未来的支持。此外,考虑到微软的官方推荐和文档,以及WebClient的一些限制和问题,选择HttpClient可能是更好的选择。
// 示例代码 HttpClient httpClient = new HttpClient(); HttpResponseMessage response = await httpClient.GetAsync(url); string result = await response.Content.ReadAsStringAsync();