关闭。这个问题需要更加集中。它目前不接受答案。想改进这个问题?更新问题,使其仅通过编辑此帖子专注于一个问题。上个月关门。社区上个月审查了是否重新打开此问题并将其关闭:原始关闭原因未解决 改进此问题
我们的 Web 应用程序在 .NET Framework 4.0 中运行。 UI 通过 Ajax 调用调用控制器方法。
我们需要使用供应商提供的 REST 服务。我正在评估在 .NET 4.0 中调用 REST 服务的最佳方式。 REST 服务需要一个基本的身份验证方案,它可以返回 XML 和 JSON 格式的数据。
上传/下载大量数据没有任何要求,我以后也看不到任何东西。我查看了一些用于 REST 消费的开源代码项目,并没有发现任何价值来证明项目中的额外依赖是合理的。我开始评估 WebClient
和 HttpClient
。我从 NuGet 下载了 .NET 4.0 的 HttpClient。
我搜索了 WebClient
和 HttpClient
之间的区别,this site 提到单个 HttpClient 可以处理并发调用,它可以重用解析的 DNS、cookie 配置和身份验证。我还没有看到我们可能因差异而获得的实际价值。
我做了一个快速的性能测试以了解 WebClient
(同步调用)、HttpClient
(同步和异步)的执行情况。结果如下:
我对所有请求(最小 - 最大)使用相同的 HttpClient
实例。
WebClient 同步:8 毫秒 - 167 毫秒 HttpClient 同步:3 毫秒 - 7228 毫秒 HttpClient 异步:985 - 10405 毫秒
为每个请求使用新的 HttpClient
(最小 - 最大):
WebClient 同步:4 毫秒 - 297 毫秒 HttpClient 同步:3 毫秒 - 7953 毫秒 HttpClient 异步:1027 - 10834 毫秒
代码
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<List<AHNData>>(sw1);
});
Parallel.For(0, batchSize, parallelOptions,
j =>
{
Stopwatch sw1 = Stopwatch.StartNew();
GetDataFromHttpClientSync<List<AHNData>>(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 Sync " + sw.ElapsedMilliseconds);
}
});
Console.Read();
}
public static T GetDataFromWebClient<T>()
{
using (var webClient = new WebClient())
{
webClient.BaseAddress = _url;
return JsonConvert.DeserializeObject<T>(
webClient.DownloadString(_url));
}
}
public static void GetDataFromHttpClientSync<T>(Stopwatch sw)
{
HttpClient httpClient = new HttpClient();
var response = httpClient.GetAsync(_url).Result;
var obj = JsonConvert.DeserializeObject<T>(
response.Content.ReadAsStringAsync().Result);
sw.Stop();
Trace.WriteLine("HttpClient Sync " + sw.ElapsedMilliseconds);
}
public static void GetDataFromHttpClientAsync<T>(Stopwatch sw)
{
HttpClient httpClient = new HttpClient();
var response = httpClient.GetAsync(_url).ContinueWith(
(a) => {
JsonConvert.DeserializeObject<T>(
a.Result.Content.ReadAsStringAsync().Result);
sw.Stop();
Trace.WriteLine("HttpClient Async " + sw.ElapsedMilliseconds);
}, TaskContinuationOptions.None);
}
}
}
我的问题
REST 调用在 3-4 秒内返回,这是可以接受的。对 REST 服务的调用是在从 Ajax 调用中调用的控制器方法中启动的。首先,调用在不同的线程中运行并且不会阻塞 UI。那么,我可以坚持使用同步调用吗?上面的代码是在我的 localbox 中运行的。在生产设置中,将涉及 DNS 和代理查找。使用 HttpClient 优于 WebClient 有优势吗? HttpClient并发比WebClient好吗?从测试结果来看,我看到 WebClient 同步调用的性能更好。如果我们升级到 .NET 4.5,HttpClient 会是更好的设计选择吗?性能是关键的设计因素。
GetDataFromHttpClientAsync
不公平,因为它首先运行,其他调用受益于潜在的缓存数据(无论是在本地计算机上还是在您和目标之间的任何透明代理上)并且速度会更快。此外,在正确的条件下,var response = httpClient.GetAsync("http://localhost:9000/api/values/").Result;
可能会由于您耗尽线程池线程而导致死锁。您永远不应该阻塞依赖于 ThreadPool 线程中的线程池的活动,您应该改为 await
以便它将线程返回到池中。
HttpClient
而不是 WebClient
进行新开发。 .NET Framework 和 .NET Core 都是如此。
HttpClient 是较新的 API,它具有以下优点:
具有良好的异步编程模型
由 Henrik F Nielson 进行工作,他基本上是 HTTP 的发明者之一,他设计了 API,因此您可以轻松地遵循 HTTP 标准,例如生成符合标准的标头
在 .NET 框架 4.5 中,因此它对可预见的未来有一定程度的支持
如果您想在其他平台(.NET 4.0、Windows Phone 等)上使用它,还具有该库的 xcopyable/portable-framework 版本。
如果您正在编写一个对其他 Web 服务进行 REST 调用的 Web 服务,您应该希望对所有 REST 调用使用异步编程模型,这样您就不会遇到线程不足的问题。您可能还想使用支持 async/await 的最新 C# 编译器。
注意:AFAIK 的性能并不高。如果您创建一个公平的测试,它可能有点类似的性能。
HttpClientFactory
评估创建 HttpClient 的不同方式很重要,其中一部分是理解 HttpClientFactory。
我知道这不是一个直接的答案 - 但你最好从这里开始而不是到处都是 new HttpClient(...)
。
对于 ASP.NET 应用程序,我仍然更喜欢 WebClient
而不是 HttpClient
,因为:
现代实现带有基于异步/等待任务的方法具有更小的内存占用和 2-5 倍的速度(其他答案已经提到)建议“在应用程序的生命周期内重用 HttpClient 的单个实例”。但是 ASP.NET 没有“应用程序的生命周期”,只有请求的生命周期。当前对 ASP.NET 5 的指导是使用 HttpClientFactory,但它只能通过依赖注入来使用。有些人想要一个更简单的解决方案。最重要的是,如果您像 MS 建议的那样在应用程序的整个生命周期中使用 HttpClient 的一个单例实例 - 它存在已知问题。例如 DNS 缓存问题 - HttpClient 只是忽略 TTL 并“永远”缓存 DNS。但是,有一些解决方法。如果您想了解有关 HttpClient 的问题和困惑的更多信息,请阅读 Microsoft GitHub 上的此评论。
only lifetime of a request.
这是错误的。在旧的 ASP.NET 堆栈中也可以使用 DI 容器来提供单例或作用域对象,只是更难使用。
首先,具体来说,我不是 WebClient 与 HttpClient 的权威。其次,从您上面的评论来看,似乎表明 WebClient 仅是同步的,而 HttpClient 两者都是同步的。
我做了一个快速的性能测试,以了解 WebClient(同步调用)、HttpClient(同步和异步)如何执行。这是结果。
在考虑未来时,我认为这是一个巨大的差异,即长时间运行的进程、响应式 GUI 等(增加您建议的 .NET 框架 4.5 的好处 - 根据我的实际经验,它在 IIS 上的速度要快得多)。
WebClient
在最新的 .NET 版本中似乎确实具有异步功能。我想知道为什么它似乎在如此大规模上胜过 HttpClient。
也许你可以换一种方式来思考这个问题。 WebClient
和 HttpClient
本质上是同一事物的不同实现。我建议在整个应用程序中实现带有 IoC Container 的 Dependency Injection pattern。您应该构造一个比低级别 HTTP 传输具有更高抽象级别的客户端接口。您可以编写同时使用 WebClient
和 HttpClient
的具体类,然后使用 IoC 容器通过配置注入实现。
这将使您能够轻松地在 HttpClient
和 WebClient
之间切换,以便您能够在生产环境中客观地进行测试。
所以像这样的问题:
如果我们升级到 .Net 4.5,HttpClient 会是更好的设计选择吗?
实际上可以通过使用 IoC 容器在两个客户端实现之间切换来客观地回答。这是您可能依赖的示例界面,其中不包含有关 HttpClient
或 WebClient
的任何详细信息。
/// <summary>
/// Dependency Injection abstraction for rest clients.
/// </summary>
public interface IClient
{
/// <summary>
/// Adapter for serialization/deserialization of http body data
/// </summary>
ISerializationAdapter SerializationAdapter { get; }
/// <summary>
/// Sends a strongly typed request to the server and waits for a strongly typed response
/// </summary>
/// <typeparam name="TResponseBody">The expected type of the response body</typeparam>
/// <typeparam name="TRequestBody">The type of the request body if specified</typeparam>
/// <param name="request">The request that will be translated to a http request</param>
/// <returns></returns>
Task<Response<TResponseBody>> SendAsync<TResponseBody, TRequestBody>(Request<TRequestBody> request);
/// <summary>
/// Default headers to be sent with http requests
/// </summary>
IHeadersCollection DefaultRequestHeaders { get; }
/// <summary>
/// Default timeout for http requests
/// </summary>
TimeSpan Timeout { get; set; }
/// <summary>
/// Base Uri for the client. Any resources specified on requests will be relative to this.
/// </summary>
Uri BaseUri { get; set; }
/// <summary>
/// Name of the client
/// </summary>
string Name { get; }
}
public class Request<TRequestBody>
{
#region Public Properties
public IHeadersCollection Headers { get; }
public Uri Resource { get; set; }
public HttpRequestMethod HttpRequestMethod { get; set; }
public TRequestBody Body { get; set; }
public CancellationToken CancellationToken { get; set; }
public string CustomHttpRequestMethod { get; set; }
#endregion
public Request(Uri resource,
TRequestBody body,
IHeadersCollection headers,
HttpRequestMethod httpRequestMethod,
IClient client,
CancellationToken cancellationToken)
{
Body = body;
Headers = headers;
Resource = resource;
HttpRequestMethod = httpRequestMethod;
CancellationToken = cancellationToken;
if (Headers == null) Headers = new RequestHeadersCollection();
var defaultRequestHeaders = client?.DefaultRequestHeaders;
if (defaultRequestHeaders == null) return;
foreach (var kvp in defaultRequestHeaders)
{
Headers.Add(kvp);
}
}
}
public abstract class Response<TResponseBody> : Response
{
#region Public Properties
public virtual TResponseBody Body { get; }
#endregion
#region Constructors
/// <summary>
/// Only used for mocking or other inheritance
/// </summary>
protected Response() : base()
{
}
protected Response(
IHeadersCollection headersCollection,
int statusCode,
HttpRequestMethod httpRequestMethod,
byte[] responseData,
TResponseBody body,
Uri requestUri
) : base(
headersCollection,
statusCode,
httpRequestMethod,
responseData,
requestUri)
{
Body = body;
}
public static implicit operator TResponseBody(Response<TResponseBody> readResult)
{
return readResult.Body;
}
#endregion
}
public abstract class Response
{
#region Fields
private readonly byte[] _responseData;
#endregion
#region Public Properties
public virtual int StatusCode { get; }
public virtual IHeadersCollection Headers { get; }
public virtual HttpRequestMethod HttpRequestMethod { get; }
public abstract bool IsSuccess { get; }
public virtual Uri RequestUri { get; }
#endregion
#region Constructor
/// <summary>
/// Only used for mocking or other inheritance
/// </summary>
protected Response()
{
}
protected Response
(
IHeadersCollection headersCollection,
int statusCode,
HttpRequestMethod httpRequestMethod,
byte[] responseData,
Uri requestUri
)
{
StatusCode = statusCode;
Headers = headersCollection;
HttpRequestMethod = httpRequestMethod;
RequestUri = requestUri;
_responseData = responseData;
}
#endregion
#region Public Methods
public virtual byte[] GetResponseData()
{
return _responseData;
}
#endregion
}
您可以使用 Task.Run
使 WebClient
在其实现中异步运行。
依赖注入,如果做得好有助于缓解必须预先做出低级决策的问题。最终,知道真正答案的唯一方法是在现场环境中尝试,看看哪一个效果最好。很有可能 WebClient
可能对某些客户更有效,而 HttpClient
可能更适合其他客户。这就是为什么抽象很重要。这意味着可以快速换入代码,或通过配置更改代码,而无需更改应用程序的基本设计。
顺便说一句:还有许多其他原因,您应该使用抽象而不是直接调用这些低级 API 之一。一个巨大的问题是单元可测试性。
Task.Run
。
我在 HttpClient、WebClient 和 HttpWebResponse 之间进行了基准测试,然后调用了 REST Web API。
结果:
调用 REST Web API 基准测试
---------------------Stage 1 ---- 10 Request
{00:00:17.2232544} ====>HttpClinet
{00:00:04.3108986} ====>WebRequest
{00:00:04.5436889} ====>WebClient
---------------------Stage 1 ---- 10 Request--Small Size
{00:00:17.2232544}====>HttpClinet
{00:00:04.3108986}====>WebRequest
{00:00:04.5436889}====>WebClient
---------------------Stage 3 ---- 10 sync Request--Small Size
{00:00:15.3047502}====>HttpClinet
{00:00:03.5505249}====>WebRequest
{00:00:04.0761359}====>WebClient
---------------------Stage 4 ---- 100 sync Request--Small Size
{00:03:23.6268086}====>HttpClinet
{00:00:47.1406632}====>WebRequest
{00:01:01.2319499}====>WebClient
---------------------Stage 5 ---- 10 sync Request--Max Size
{00:00:58.1804677}====>HttpClinet
{00:00:58.0710444}====>WebRequest
{00:00:38.4170938}====>WebClient
---------------------Stage 6 ---- 10 sync Request--Max Size
{00:01:04.9964278}====>HttpClinet
{00:00:59.1429764}====>WebRequest
{00:00:32.0584836}====>WebClient
WebClient 更快
var stopWatch = new Stopwatch();
stopWatch.Start();
for (var i = 0; i < 10; ++i)
{
CallGetHttpClient();
CallPostHttpClient();
}
stopWatch.Stop();
var httpClientValue = stopWatch.Elapsed;
stopWatch = new Stopwatch();
stopWatch.Start();
for (var i = 0; i < 10; ++i)
{
CallGetWebRequest();
CallPostWebRequest();
}
stopWatch.Stop();
var webRequesttValue = stopWatch.Elapsed;
stopWatch = new Stopwatch();
stopWatch.Start();
for (var i = 0; i < 10; ++i)
{
CallGetWebClient();
CallPostWebClient();
}
stopWatch.Stop();
var webClientValue = stopWatch.Elapsed;
//-------------------------Functions
private void CallPostHttpClient()
{
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://localhost:44354/api/test/");
var responseTask = httpClient.PostAsync("PostJson", null);
responseTask.Wait();
var result = responseTask.Result;
var readTask = result.Content.ReadAsStringAsync().Result;
}
private void CallGetHttpClient()
{
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://localhost:44354/api/test/");
var responseTask = httpClient.GetAsync("getjson");
responseTask.Wait();
var result = responseTask.Result;
var readTask = result.Content.ReadAsStringAsync().Result;
}
private string CallGetWebRequest()
{
var request = (HttpWebRequest)WebRequest.Create("https://localhost:44354/api/test/getjson");
request.Method = "GET";
request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
var content = string.Empty;
using (var response = (HttpWebResponse)request.GetResponse())
{
using (var stream = response.GetResponseStream())
{
using (var sr = new StreamReader(stream))
{
content = sr.ReadToEnd();
}
}
}
return content;
}
private string CallPostWebRequest()
{
var apiUrl = "https://localhost:44354/api/test/PostJson";
HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(new Uri(apiUrl));
httpRequest.ContentType = "application/json";
httpRequest.Method = "POST";
httpRequest.ContentLength = 0;
using (var httpResponse = (HttpWebResponse)httpRequest.GetResponse())
{
using (Stream stream = httpResponse.GetResponseStream())
{
var json = new StreamReader(stream).ReadToEnd();
return json;
}
}
return "";
}
private string CallGetWebClient()
{
string apiUrl = "https://localhost:44354/api/test/getjson";
var client = new WebClient();
client.Headers["Content-type"] = "application/json";
client.Encoding = Encoding.UTF8;
var json = client.DownloadString(apiUrl);
return json;
}
private string CallPostWebClient()
{
string apiUrl = "https://localhost:44354/api/test/PostJson";
var client = new WebClient();
client.Headers["Content-type"] = "application/json";
client.Encoding = Encoding.UTF8;
var json = client.UploadString(apiUrl, "");
return json;
}
GetResponse()
calls HttpClient
underneath 和 blocks 与 .GetAwaiter().GetResult()
不定期副业成功案例分享
WebClient
提供的 documentation,“我们不建议您使用WebClient
用于新开发的类。改为使用System.Net.Http.HttpClient
类。”