Decidere tra HttpClient e WebClient


219

La nostra app Web è in esecuzione in .Net Framework 4.0. L'interfaccia utente chiama i metodi del controller tramite chiamate ajax.

Dobbiamo consumare il servizio REST dal nostro fornitore. Sto valutando il modo migliore per chiamare il servizio REST in .Net 4.0. Il servizio REST richiede uno schema di autenticazione di base e può restituire dati sia in XML che in JSON. Non è necessario caricare / scaricare dati enormi e non vedo nulla in futuro. Ho dato un'occhiata ad alcuni progetti di codice open source per il consumo REST e non ho trovato alcun valore in quelli che giustifichino una dipendenza aggiuntiva nel progetto. Iniziato a valutare WebCliente HttpClient. Ho scaricato HttpClient per .Net 4.0 da NuGet.

Ho cercato differenze tra WebCliente HttpCliente questo sito ha menzionato il fatto che singolo HttpClient può gestire chiamate simultanee e può riutilizzare DNS risolto, configurazione dei cookie e autenticazione. Devo ancora vedere i valori pratici che potremmo ottenere a causa delle differenze.

Ho eseguito un rapido test delle prestazioni per scoprire come si comportano WebClient(sincronizzazione chiamate), HttpClient(sincronizzazione e asincronizzazione). e qui ci sono i risultati:

Utilizzo della stessa HttpClientistanza per tutte le richieste (min - max)

Sincronizzazione WebClient: 8 ms - 167 ms
Sincronizzazione HttpClient: 3 ms - 7228 ms
Asincronizzazione HttpClient: 985 - 10405 ms

Utilizzo di un nuovo HttpClientper ogni richiesta (min - max)

Sincronizzazione WebClient: 4 ms - 297 ms
Sincronizzazione HttpClient: 3 ms - 7953 ms
Sincronizzazione HttpClient: 1027 - 10834 ms

Codice

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);
        }
    }
}

Le mie domande

  1. Le chiamate REST ritornano in 3-4s, il che è accettabile. Le chiamate al servizio REST vengono avviate nei metodi del controller che vengono invocati dalle chiamate Ajax. Per cominciare, le chiamate vengono eseguite in un thread diverso e non bloccano l'interfaccia utente. Quindi, posso semplicemente attenermi alle chiamate di sincronizzazione?
  2. Il codice sopra è stato eseguito nella mia localbox. Nella configurazione del prod, saranno coinvolti la ricerca DNS e proxy. C'è qualche vantaggio nell'usare HttpClientover WebClient?
  3. La HttpClientconcorrenza è migliore di WebClient? Dai risultati del test, vedo che le WebClientchiamate di sincronizzazione funzionano meglio.
  4. Sarà HttpClientuna scelta di progettazione migliore se eseguiamo l'aggiornamento a .Net 4.5? Le prestazioni sono il fattore chiave di progettazione.

5
Il tuo test non è giusto GetDataFromHttpClientAsyncperché viene eseguito per primo, le altre invocazioni trarranno vantaggio dal potenziale possesso di dati cahed (sia sul computer locale o qualsiasi proxy trasparente tra te e la destinazione) e saranno più veloci. Inoltre, nelle giuste condizioni var response = httpClient.GetAsync("http://localhost:9000/api/values/").Result;può causare un deadlock a causa dell'esaurimento dei thread del threadpool. Non dovresti mai bloccare un'attività che dipende dal pool di thread nei thread ThreadPool, dovresti awaitinvece restituire il thread al pool.
Scott Chamberlain,

1
HttpClient con Web API Client è fantastico per un client REST JSON / XML.
Cory Nelson,

@Scott Chamberlain - Grazie per la risposta. Poiché tutte le chiamate di prova vengono eseguite in Parallel.Foreach, non vi è alcuna garanzia su quale sarebbe stato eseguito per primo. Inoltre, se la prima chiamata al servizio proveniva da GetDataFromHttpClientAsync, tutte le chiamate successive da GetDataFromHttpClientAsync avrebbero dovuto beneficiare della cache ed essere eseguite più rapidamente. Non l'ho visto nel risultato. Rgd aspetta, stiamo ancora usando 4.0. Sono d'accordo con te sul fatto che HttpClient in modo sincronizzato porterebbe a un deadlock e sto escludendo questa opzione dalla mia considerazione progettuale.
user3092913

@CoryNelson Puoi per favore spiegare perché HttpClient con Web API Client è fantastico per un client REST JSON / XML?
user3092913

2
Ecco alcune parole sulla differenza tra HttpClient e WebClient: blogs.msdn.com/b/henrikn/archive/2012/02/11/…
JustAndrei,

Risposte:


243

Vivo in entrambi i mondi F # e Web API.

Ci sono molte cose interessanti che accadono con l'API Web, specialmente sotto forma di gestori di messaggi per la sicurezza, ecc.

So che la mia è solo un'opinione, ma consiglierei di usarla solo HttpClientper qualsiasi lavoro futuro . Forse c'è un modo per sfruttare alcuni degli altri pezzi che escono System.Net.Httpsenza usare direttamente quell'assemblea, ma non riesco a immaginare come funzionerebbe in questo momento.

Parlando di confrontare questi due

  • HttpClient è più vicino a HTTP di WebClient.
  • HttpClient non intendeva sostituire completamente il client Web, poiché ci sono cose come l'avanzamento dei report, lo schema URI personalizzato e le chiamate FTP fornite da WebClient, ma HttpClient no.
+--------------------------------------------+--------------------------------------------+
|               WebClient                    |               HttpClient                   |
+--------------------------------------------+--------------------------------------------+
| Available in older versions of .NET        | .NET 4.5 only.  Created to support the     |
|                                            | growing need of the Web API REST calls     |
+--------------------------------------------+--------------------------------------------+
| WinRT applications cannot use WebClient    | HTTPClient can be used with WinRT          |
+--------------------------------------------+--------------------------------------------+
| Provides progress reporting for downloads  | No progress reporting for downloads        |
+--------------------------------------------+--------------------------------------------+
| Does not reuse resolved DNS,               | Can reuse resolved DNS, cookie             |
| configured cookies                         | configuration and other authentication     |
+--------------------------------------------+--------------------------------------------+
| You need to new up a WebClient to          | Single HttpClient can make concurrent      |
| make concurrent requests.                  | requests                                   |
+--------------------------------------------+--------------------------------------------+
| Thin layer over WebRequest and             | Thin layer of HttpWebRequest and           |
| WebResponse                                | HttpWebResponse                            |
+--------------------------------------------+--------------------------------------------+
| Mocking and testing WebClient is difficult | Mocking and testing HttpClient is easy     |
+--------------------------------------------+--------------------------------------------+
| Supports FTP                               | No support for FTP                         |
+--------------------------------------------+--------------------------------------------+
| Both Synchronous and Asynchronous methods  | All IO bound methods in                    |
| are available for IO bound requests        | HTTPClient are asynchronous                |
+--------------------------------------------+--------------------------------------------+

Se si utilizza .NET 4.5, utilizzare la bontà asincrona con HttpClient fornita da Microsoft agli sviluppatori. HttpClient è molto simmetrico rispetto ai fratelli lato server dell'HTTP che sono HttpRequest e HttpResponse.

Aggiornamento: 5 motivi per utilizzare la nuova API HttpClient:

  • Intestazioni fortemente tipizzate.
  • Cache, cookie e credenziali condivisi
  • Accesso a cookie e cookie condivisi
  • Controllo su cache e cache condivisa.
  • Iniettare il modulo di codice nella pipeline ASP.NET. Codice più pulito e modulare.

Riferimento

C # 5.0 Joseph Albahari

(Channel9 - Video Build 2013)

Cinque ottimi motivi per utilizzare la nuova API HttpClient per connettersi ai servizi Web

WebClient vs HttpClient vs HttpWebRequest


4
Va sottolineato che HttpClient è disponibile anche per .NET 4.0 .
Todd Menier,

2
Questo non spiega perché WebClient sembra essere più veloce di HttpClient. WebClientSembra anche avere metodi asincroni ora.
schiaccia il

8
@crush è perché l'OP sta creando una nuova istanza di HttpClient per ogni singola richiesta. Invece dovresti usare una singola istanza di HttpClient per la durata della tua applicazione. Vedi stackoverflow.com/a/22561368/57369
Gabriel,

6
Vale la pena notare che WebClientnon è disponibile .Net Corema lo HttpClientè.
Pranav Singh,

3
Dal momento che .Net Core 2.0 WebClient (tra migliaia di altre API) è tornato e disponibile.
CoderBang

56

HttpClient è la più recente delle API e ha i vantaggi di

  • ha un buon modello di programmazione asincrona
  • lavorato su Henrik F Nielson, che è fondamentalmente uno degli inventori di HTTP, e ha progettato l'API in modo che sia facile seguire lo standard HTTP, ad esempio generando intestazioni conformi agli standard
  • è nel framework .Net 4.5, quindi ha un certo livello di supporto garantito per il prossimo futuro
  • ha anche la versione xcopyable / portable-framework della libreria se vuoi usarla su altre piattaforme - .Net 4.0, Windows Phone ecc.

Se stai scrivendo un servizio Web che sta effettuando chiamate REST ad altri servizi Web, dovresti utilizzare un modello di programmazione asincrona per tutte le tue chiamate REST, in modo da non colpire la fame di thread. Probabilmente vuoi anche usare il più recente compilatore C # che ha il supporto asincrono / attendi.

Nota: non è più performante AFAIK. Probabilmente è in qualche modo simile se crei un test corretto.


Se avesse un modo di cambiare proxy sarebbe folle
ed22

3

In primo luogo, non sono un'autorità su WebClient rispetto a HttpClient, in particolare. In secondo luogo, dai tuoi commenti sopra, sembra suggerire che WebClient è SOLO sincronizzazione, mentre HttpClient è entrambi.

Ho eseguito un rapido test delle prestazioni per scoprire le prestazioni di WebClient (Sync Calls), HttpClient (Sync e Async). e qui ci sono i risultati.

Vedo che è una grande differenza quando si pensa al futuro, ad esempio processi di lunga durata, GUI reattiva, ecc. (Aggiungere ai vantaggi che suggerisce il framework 4.5 - che nella mia esperienza reale è molto più veloce su IIS)


4
WebClientsembra avere capacità asincrone nelle ultime versioni di .NET. Mi piacerebbe sapere perché sembra avere un rendimento superiore a HttpClient su così vasta scala.
schiaccia il

1
Secondo stackoverflow.com/a/4988325/1662973 , sembra essere lo stesso, a parte il fatto che uno è un'astrazione dell'altro. Forse dipende da come vengono usati / caricati gli oggetti. Il tempo minimo supporta l'affermazione secondo cui WebClient è in realtà un'astrazione di HttpClient, quindi un sovraccarico di millisecondi. Il framework potrebbe essere "subdolo" nel modo in cui si sta davvero raggruppando o eliminando il client web.
Anthony Horne,


2

Ho un benchmark tra HttpClient, WebClient, HttpWebResponse quindi chiamo Rest Web Api

e risultato Benchmark delle API Web di Rest Rest delle chiamate

--------------------- Fase 1 ---- 10 Richiesta

{00: 00: 17.2232544} ====> HttpClinet

{00: 00: 04.3108986} ====> WebRequest

{00: 00: 04.5436889} ====> WebClient

--------------------- Fase 1 ---- 10 Richiesta - Dimensioni ridotte

{00: 00: 17,2,232544 millions} ====> HttpClinet

{00: 00: 04,3108986} ====> WebRequest

{00: 00: 04,5436889} ====> WebClient

--------------------- Fase 3 ---- Richiesta di sincronizzazione 10 - Dimensioni ridotte

{00: 00: 15,3,047502 millions} ====> HttpClinet

{00: 00: 03,5505249} ====> WebRequest

{00: 00: 04,0761359} ====> WebClient

--------------------- Fase 4 ---- 100 richieste di sincronizzazione - Dimensioni ridotte

{00: 03: 23,6,268086 millions} ====> HttpClinet

{00: 00: 47,1,406632 millions} ====> WebRequest

{00: 01: 01,2319499} ====> WebClient

--------------------- Fase 5 ---- Richiesta di sincronizzazione 10 - Dimensione massima

{00: 00: 58,1,804677 millions} ====> HttpClinet

{00: 00: 58,0,710444 millions} ====> WebRequest

{00: 00: 38,4,170938 millions} ====> WebClient

--------------------- Fase 6 ---- Richiesta di sincronizzazione 10 - Dimensione massima

{00: 01: 04,9964278} ====> HttpClinet

{00: 00: 59,1,429764 millions} ====> WebRequest

{00: 00: 32,0,584836 millions} ====> WebClient

_____ WebClient è più veloce ()

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;

// ------------------------- Funzioni

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;
    }

1
Vedi il commento di Gabriel sopra. In breve, HttpClient è molto più veloce se crei un'istanza di HttpClient e la riutilizzi.
LT Dan,

1

Forse potresti pensare al problema in un modo diverso. WebCliente HttpClientsono essenzialmente diverse implementazioni della stessa cosa. Quello che raccomando è implementare il modello di Iniezione delle dipendenze con un contenitore IoC in tutta l'applicazione. È necessario costruire un'interfaccia client con un livello di astrazione più elevato rispetto al trasferimento HTTP di basso livello. È possibile scrivere classi concrete che utilizzano entrambi WebCliente HttpClient, quindi utilizzare il contenitore IoC per iniettare l'implementazione tramite config.

Ciò che ciò ti consentirebbe di fare sarebbe passare da HttpCliente WebClientfacilmente in modo da poter eseguire test oggettivi nell'ambiente di produzione.

Quindi domande come:

HttpClient sarà una scelta di progettazione migliore se eseguiamo l'aggiornamento a .Net 4.5?

In realtà, si può rispondere in modo obiettivo passando da una implementazione a due client usando il contenitore IoC. Ecco un'interfaccia di esempio da cui potresti dipendere che non include dettagli su HttpCliento 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
}

Codice completo

Implementazione di HttpClient

È possibile utilizzare Task.Runper WebClienteseguire in modo asincrono nella sua implementazione.

L'iniezione di dipendenza, se eseguita bene, aiuta ad alleviare il problema di dover prendere decisioni di basso livello in anticipo. Alla fine, l'unico modo per conoscere la vera risposta è provare sia in un ambiente live sia vedere quale funziona meglio. È del tutto possibile che WebClientpotrebbe funzionare meglio per alcuni clienti e HttpClientpotrebbe funzionare meglio per altri. Questo è il motivo per cui l'astrazione è importante. Significa che il codice può essere rapidamente scambiato o modificato con la configurazione senza cambiare il design fondamentale dell'app.


1

Opinione impopolare dal 2020:

Quando si tratta di ASP.NET apps io preferisco ancora WebClientoltre HttpClient, perché:

  1. L'implementazione moderna prevede metodi asincroni / attendibili basati su attività
  2. Ha un ingombro di memoria ridotto e 2x-5 volte più veloce (altre risposte lo menzionano già)
  3. Si suggerisce di " riutilizzare una singola istanza di HttpClient per la durata della propria applicazione ". Ma ASP.NET non ha "durata di applicazione", solo durata di una richiesta.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.