在HttpClient和WebClient之间做出决定

use*_*913 204 .net c# rest webclient dotnet-httpclient

我们的网络应用程序在.Net Framework 4.0中运行.UI通过ajax调用调用控制器方法.

我们需要从供应商处使用REST服务.我正在评估在.Net 4.0中调用REST服务的最佳方法.REST服务需要基本身份验证方案,它可以返回XML和JSON中的数据.没有要求上传/下载大量数据,我将来也看不到任何东西.我看了几个用于REST消费的开源代码项目,并没有找到任何值来证明项目中的额外依赖性.开始评估WebClientHttpClient.我从NuGet下载了用于.Net 4.0的HttpClient.

我搜索了WebClient和之间的差异,HttpClient并且该网站提到单个HttpClient可以处理并发调用,它可以重用已解析的DNS,cookie配置和身份验证.我还没有看到由于差异我们可能获得的实用价值.

我做了一个快速的性能测试,以找到WebClient(同步调用),HttpClient(同步和异步)如何执行.以下是结果:

HttpClient对所有请求使用相同的实例(min - max)

WebClient同步:8毫秒 - 167毫秒
HttpClient同步:3毫秒 - 7228毫秒
HttpClient异步:985 - 10405毫秒

HttpClient为每个请求使用new (min - max)

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我的问题

  1. REST调用以3-4s返回,这是可以接受的.调用REST服务是在从ajax调用调用的控制器方法中启动的.首先,调用在不同的线程中运行,不会阻止UI.那么,我可以坚持同步电话吗?
  2. 上面的代码在我的localbox中运行.在prod设置中,将涉及DNS和代理查找.使用HttpClient结束有什么好处WebClient吗?
  3. HttpClient并发好过WebClient?从测试结果中,我看到 WebClient同步调用表现更好.
  4. HttpClient如果我们升级到.Net 4.5,将是一个更好的设计选择吗?性能是关键的设计因素.

Ana*_*bhi 221

我生活在F#和Web API世界中.

Web API发生了很多好事,特别是以安全性的消息处理程序等形式.

我知道我的意见只有一个,但我只建议HttpClient将来用于任何工作.也许有一些方法可以利用其他部分System.Net.Http而不直接使用该程序集,但我无法想象这将如何起作用.

说到比较这两个

  • HttpClient比WebClient更接近HTTP.
  • HttpClient并不是Web客户端的完全替代品,因为有些报告进度,自定义URI方案以及WebClient提供的FTP调用 - 但是HttpClient没有.

在此输入图像描述

如果您使用的是.NET 4.5,请使用Microsoft为开发人员提供的HttpClient的async优度.HttpClient与HTTP的服务器端兄弟非常对称,即HttpRequest和HttpResponse.

更新:使用新HttpClient API的5个理由:

  • 强类型标题.
  • 共享缓存,cookie和凭据
  • 访问cookie和共享cookie
  • 控制缓存和共享缓存.
  • 将代码模块注入ASP.NET管道.更清晰和模块化的代码.

参考

C#5.0 Joseph Albahari

(Channel9 - Video Build 2013)

使用新的HttpClient API连接到Web服务的五大理由

WebClient vs HttpClient vs HttpWebRequest

  • @crush是因为OP正在为每个请求创建一个新的HttpClient实例.相反,您应该在应用程序的生命周期中使用单个HttpClient实例.请参见http://stackoverflow.com/a/22561368/57369 (8认同)
  • 值得注意的是`.WebClient`在`.Net Core`中不可用,但``HttpClient`是. (6认同)
  • 应该提到的是,HttpClient [可用于.NET 4.0](http://www.nuget.org/packages/Microsoft.Net.Http)也是如此. (3认同)
  • 这并不能解释为什么WebClient似乎比HttpClient更快.此外,[`WebClient`](https://msdn.microsoft.com/en-us/library/system.net.webclient(v = vs.110).aspx)现在似乎有异步方法. (2认同)
  • 由于.Net Core 2.0 WebClient(在数千个其他API中)已经恢复并且可用. (2认同)

Tim*_*ith 56

HttpClient是最新的API,它具有以下优点

  • 有一个很好的异步编程模型
  • 由Henrik F Nielson工作,他基本上是HTTP的发明者之一,他设计了API,因此您可以轻松地遵循HTTP标准,例如生成符合标准的标头
  • 在.Net框架4.5中,因此它对可预见的未来有一定程度的支持
  • 如果你想在其他平台上使用它,还有xcopyable/portable-framework版本的库 - .Net 4.0,Windows Phone等.

如果您正在编写一个对其他Web服务进行REST调用的Web服务,那么您应该为所有REST调用使用异步编程模型,这样就不会遇到线程饥饿问题.您可能还想使用具有async/await支持的最新C#编译器.

注意:它不是更高性能的AFAIK.如果你创建一个公平的测试,它可能在某种程度上同样高效.

  • 虽然这是一个老问题,但它出现在我的搜索中,所以我想我应该指出 Microsoft 的[文档](https://learn.microsoft.com/en-us/dotnet/api/system.net. webclient?view=net-5.0) 对于 .NET 5 中的 `WebClient` 声明,“我们不建议您使用 `WebClient` 类进行新开发。而是使用 `System.Net.Http.HttpClient` 类”。 (15认同)

Sim*_*ver 9

HttpClientFactory

评估创建 HttpClient 的不同方式很重要,其中一部分是了解 HttpClientFactory。

https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests

我知道这不是一个直接的答案 - 但你最好从这里开始而不是new HttpClient(...)到处结束。

  • 值得强调的是,即使是 .NET Framework 中的 HttpWebRequest 和扩展 WebClient [使用 HttpClient](https://github.com/dotnet/corefx/blob/e0ba7aa8026280ee3571179cc06431baf1dfaaac/src/System.Net.Requests/src/System/Net/HttpWebRequest .cs#L1005)至少从 2018 年开始,所以这个问题本质上是没有意义的 (4认同)

Ant*_*rne 6

首先,我不是 WebClient 与 HttpClient 的权威,特别是。其次,从你上面的评论来看,似乎表明 WebClient 是 Sync ONLY 而 HttpClient 两者都是。

我做了一个快速的性能测试,以了解 WebClient(同步调用)、HttpClient(同步和异步)的执行情况。这是结果。

我认为这是在考虑未来时的巨大差异,即长时间运行的进程、响应式 GUI 等(添加到框架 4.5 建议的好处 - 在我的实际经验中,在 IIS 上速度要快得多)

  • [`WebClient`](https://msdn.microsoft.com/en-us/library/system.net.webclient(v=vs.110).aspx) 在最新的 .NET 版本中似乎确实具有异步功能。我想知道为什么它似乎在如此大规模上优于 HttpClient。 (4认同)

Mel*_*per 5

也许你可以用不同的方式来思考这个问题。WebClient本质HttpClient上是同一事物的不同实现。我建议在整个应用程序中使用IoC 容器实现依赖注入模式。您应该构建一个比低级别 HTTP 传输具有更高抽象级别的客户端接口。您可以编写同时使用和 的具体类,然后使用 IoC 容器通过 config 注入实现。WebClientHttpClient

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
}
Run Code Online (Sandbox Code Playgroud)

完整代码

HttpClient 实现

您可以在其实现中使用Task.Runmake异步运行。WebClient

依赖注入如果做得好,有助于缓解必须预先做出低级决策的问题。最终,了解真正答案的唯一方法是在现场环境中进行尝试,看看哪一个效果最好。很有可能这WebClient对某些客户来说效果更好,而HttpClient对另一些客户来说可能效果更好。这就是为什么抽象很重要。这意味着代码可以快速换入或更改配置,而无需更改应用程序的基本设计。

顺便说一句:还有许多其他原因表明您应该使用抽象而不是直接调用这些低级 API 之一。其中一个重要因素是单元可测试性。


Ale*_*lex 5

2020 年不受欢迎的观点:

当谈到ASP.NET 应用程序时,我仍然更喜欢WebClientHttpClient因为:

  1. 现代实现带有基于异步/等待任务的方法
  2. 内存占用更小,速度提高 2-5 倍(其他答案已经提到)
  3. 建议“在应用程序的整个生命周期内重用 HttpClient 的单个实例”。但是 ASP.NET 没有“应用程序的生命周期”,只有请求的生命周期。

  • “只有一个请求的生命周期。”这是错误的。在较旧的 ASP.NET 堆栈中也可以使用 DI 容器来提供单例或作用域对象,只是更难使用。 (5认同)
  • 鉴于 .NET Old 已被 .NET Core 取代,您是否使用 .NET Core 运行过基准测试?到目前为止,HttpWebRequest 是 HttpClient 的包装器,因此 WebClient 本质上是 WebClient 的遗留适配器 (4认同)
  • 另外,“.NET Old 已被 .NET Core 取代”——它还没有“取代”它,.NET Framework 仍然受到支持,并且至少还会再支持 10 年(基本上只要它是视窗)。但我可能应该表明我的答案是针对 .NET Framework,而不是 Core (2认同)