Qual é a sobrecarga de criar um novo HttpClient por chamada em um cliente WebAPI?

Qual deve ser a vida útil do HttpClient de um cliente WebAPI?
É melhor ter uma instância do HttpClient para várias chamadas?

Qual é a sobrecarga de criar e descartar um HttpClient por solicitação, como no exemplo abaixo (extraído de http://www.asp.net/web-api/overview/web-api-clients/calling-a-web-api-from -a-net-client ):

 using (var client = new HttpClient()) { client.BaseAddress = new Uri("http://localhost:9000/"); client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); // New code: HttpResponseMessage response = await client.GetAsync("api/products/1"); if (response.IsSuccessStatusCode) { Product product = await response.Content.ReadAsAsync>Product>(); Console.WriteLine("{0}\t${1}\t{2}", product.Name, product.Price, product.Category); } } 

    HttpClient foi projetado para ser reutilizado para várias chamadas . Mesmo em vários segmentos. O HttpClientHandler tem credenciais e cookies que se destinam a ser reutilizados em chamadas. Ter uma nova instância do HttpClient requer a redefinição de todas essas coisas. Além disso, a propriedade DefaultRequestHeaders contém propriedades destinadas a várias chamadas. Ter que redefinir esses valores em cada solicitação anula o ponto.

    Outro grande benefício do HttpClient é a capacidade de adicionar HttpMessageHandlers ao pipeline de solicitação / resposta para aplicar as questões de corte transversal. Estes podem ser para registro, auditoria, otimização, manipulação de redirecionamento, manuseio offline, captura de métricas. Todos os tipos de coisas diferentes. Se um novo HttpClient for criado em cada solicitação, todos esses manipuladores de mensagens precisarão ser configurados em cada solicitação e, de alguma forma, qualquer estado no nível do aplicativo compartilhado entre os pedidos desses manipuladores também precisará ser fornecido.

    Quanto mais você usar os resources do HttpClient , mais verá que a reutilização de uma instância existente faz sentido.

    No entanto, o maior problema, na minha opinião, é que quando uma class HttpClient é descartada, ela descarta o HttpClientHandler , que então fecha a TCP/IP no pool de conexões que é gerenciado pelo ServicePointManager . Isso significa que cada solicitação com um novo HttpClient requer o restabelecimento de uma nova TCP/IP .

    Dos meus testes, usando HTTP simples em uma LAN, o impacto no desempenho é praticamente insignificante. Eu suspeito que isso é porque há um keepalive TCP subjacente que está mantendo a conexão aberta mesmo quando HttpClientHandler tenta fechá-lo.

    Em solicitações que passam pela internet, tenho visto uma história diferente. Eu tenho visto um desempenho de 40% devido a ter que reabrir o pedido todas as vezes.

    Eu suspeito que o hit em uma conexão HTTPS seria ainda pior.

    Meu conselho é manter uma instância do HttpClient durante a vida útil do seu aplicativo para cada API distinta à qual você se conecta.

    Se você quiser que seu aplicativo seja dimensionado, a diferença é enorme! Dependendo da carga, você verá números de desempenho muito diferentes. Como Darrel Miller menciona, o HttpClient foi projetado para ser reutilizado em todas as solicitações. Isso foi confirmado por caras da equipe da BCL que o escreveram.

    Um projeto recente que tive foi ajudar uma varejista de computadores on-line muito grande e bem conhecida a se expandir para o tráfego da Black Friday / holiday em alguns novos sistemas. Nós nos deparamos com alguns problemas de desempenho em torno do uso do HttpClient. Como ele implementa IDisposable , os desenvolvedores fizeram o que você faria normalmente criando uma instância e colocando-a dentro de uma instrução using() . Assim que começamos o teste de carga, o aplicativo deixou o servidor de joelhos – sim, o servidor não apenas o aplicativo. A razão é que todas as instâncias do HttpClient abrem uma porta no servidor. Devido à finalização não determinística do GC e ao fato de que você está trabalhando com resources de computador que abrangem várias camadas OSI , o fechamento de portas de rede pode demorar um pouco. Na verdade, o próprio sistema operacional Windows pode levar até 20 segundos para fechar uma porta (por Microsoft). Estávamos abrindo as portas mais rapidamente do que elas poderiam ser fechadas – esgotamento da porta do servidor que atingiu a CPU em 100%. Minha correção foi alterar o HttpClient para uma instância estática que resolveu o problema. Sim, é um recurso descartável, mas qualquer sobrecarga é largamente compensada pela diferença no desempenho. Incentivo você a fazer alguns testes de carga para ver como seu aplicativo se comporta.

    Você também pode verificar a página Orientação do WebAPI para documentação e exemplos em https://www.asp.net/web-api/overview/advanced/calling-a-web-api-from-a-net-client

    Preste atenção especial a esta chamada:

    O HttpClient deve ser instanciado uma vez e reutilizado durante a vida útil de um aplicativo. Especialmente em aplicativos de servidor, a criação de uma nova instância de HttpClient para cada solicitação esgotará o número de sockets disponíveis sob cargas pesadas. Isso resultará em erros de SocketException.

    Se você achar que precisa usar um HttpClient estático com headers diferentes, endereço base etc., o que você precisará fazer é criar o HttpRequestMessage manualmente e definir esses valores no HttpRequestMessage . Em seguida, use o HttpClient:SendAsync(HttpRequestMessage requestMessage, ...)

    Como as outras respostas afirmam, o HttpClient é destinado a reutilização. No entanto, a reutilização de uma única instância HttpClient em um aplicativo multithread significa que você não pode alterar os valores de suas propriedades com informações de estado, como BaseAddress e DefaultRequestHeaders (para usá-las somente se elas forem constantes em seu aplicativo).

    Uma abordagem para contornar essa limitação é HttpClient com uma class que duplica todos os methods HttpClient necessários ( GetAsync , PostAsync etc) e os delega a um HttpClient singleton. No entanto, isso é bastante entediante (você também precisará include os methods de extensão ) e, felizmente, existe outra maneira – continuar criando novas instâncias de HttpClient , mas reutilizar o HttpClientHandler subjacente. Apenas certifique-se de não eliminar o manipulador:

     HttpClientHandler _sharedHandler = new HttpClientHandler(); //never dispose this HttpClient GetClient(string token) { //client code can dispose these HttpClient instances return new HttpClient(_sharedHandler, disposeHandler: false) { DefaultRequestHeaders = { Authorization = new AuthenticationHeaderValue("Bearer", token) } }; } 

    Relacionado a sites de alto volume, mas não diretamente ao HttpClient. Temos o snippet de código abaixo em todos os nossos serviços.

     // number of milliseconds after which an active System.Net.ServicePoint connection is closed. const int DefaultConnectionLeaseTimeout = 60000; ServicePoint sp = ServicePointManager.FindServicePoint(new Uri("http://")); sp.ConnectionLeaseTimeout = DefaultConnectionLeaseTimeout; 

    Em https://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k(System.Net.ServicePoint.ConnectionLeaseTimeout);k(TargetFrameworkMoniker-.NETFramework,Version%3Dv4.5.2); k (DevLang-csharp) & rd = true

    “Você pode usar essa propriedade para garantir que as conexões ativas de um object do ServicePoint não permaneçam abertas indefinidamente. Essa propriedade é destinada a cenários em que as conexões devem ser descartadas e restabelecidas periodicamente, como cenários de balanceamento de carga.

    Por padrão, quando KeepAlive é verdadeiro para uma solicitação, a propriedade MaxIdleTime define o tempo limite para o fechamento de conexões do ServicePoint devido à inatividade. Se o ServicePoint tiver conexões ativas, o MaxIdleTime não terá efeito e as conexões permanecerão abertas indefinidamente.

    Quando a propriedade ConnectionLeaseTimeout é definida como um valor diferente de -1 e após o tempo especificado, uma conexão ativa do ServicePoint é fechada após atender a uma solicitação, definindo KeepAlive como false nessa solicitação. Definir esse valor afeta todas as conexões gerenciadas pelo object ServicePoint. ”

    Quando você tem serviços atrás de um CDN ou outro endpoint que deseja fazer failover, essa configuração ajuda os chamadores a seguir você até o novo destino. Neste exemplo, 60 segundos após um failover, todos os chamadores devem se reconectar ao novo endpoint. Isso requer que você conheça seus serviços dependentes (os serviços que você chama) e seus pontos de extremidade.

    Você também pode querer se referir a esta postagem do blog por Simon Timms: https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/

    Mas o HttpClient é diferente. Embora implemente a interface IDisposable , ela é na verdade um object compartilhado. Isso significa que sob as capas é reentrante) e thread seguro. Em vez de criar uma nova instância de HttpClient para cada execução, você deve compartilhar uma única instância do HttpClient durante toda a vida útil do aplicativo. Vamos ver o porquê.