Recentemente me deparei com este post de monstros asp.net que fala sobre problemas com o uso HttpClient
da seguinte maneira:
using(var client = new HttpClient())
{
}
De acordo com a postagem do blog, se descartamos a HttpClient
solicitação após cada solicitação, ela pode manter as conexões TCP abertas. Isso pode potencialmente levar a System.Net.Sockets.SocketException
.
A maneira correta conforme a postagem é criar uma única instância, HttpClient
pois isso ajuda a reduzir o desperdício de soquetes.
Da postagem:
Se compartilharmos uma única instância do HttpClient, podemos reduzir o desperdício de soquetes reutilizando-os:
namespace ConsoleApplication { public class Program { private static HttpClient Client = new HttpClient(); public static void Main(string[] args) { Console.WriteLine("Starting connections"); for(int i = 0; i<10; i++) { var result = Client.GetAsync("http://aspnetmonsters.com").Result; Console.WriteLine(result.StatusCode); } Console.WriteLine("Connections done"); Console.ReadLine(); } } }
Sempre descartei o HttpClient
objeto depois de usá-lo, pois senti que essa é a melhor maneira de usá-lo. Mas esta postagem no blog agora me faz sentir que eu estava fazendo errado todo esse tempo.
Devemos criar uma nova instância única HttpClient
para todas as solicitações? Existem armadilhas no uso de instância estática?
fonte
Close()
ou iniciar um novoGet()
. Se você simplesmente eliminar o cliente quando terminar, não haverá ninguém para lidar com esse aperto de mão de fechamento e todas as suas portas terão o estado TIME_WAIT, por causa disso.Respostas:
Parece uma publicação atraente no blog. No entanto, antes de tomar uma decisão, primeiro eu executava os mesmos testes que o autor do blog, mas em seu próprio código. Eu também tentaria descobrir um pouco mais sobre o HttpClient e seu comportamento.
Esta publicação afirma:
Portanto, o que provavelmente está acontecendo quando um HttpClient é compartilhado é que as conexões estão sendo reutilizadas, o que é bom se você não precisar de conexões persistentes. A única maneira de saber com certeza se isso importa ou não para sua situação é executar seus próprios testes de desempenho.
Se você cavar, encontrará vários outros recursos que abordam esse problema (incluindo um artigo de práticas recomendadas da Microsoft), por isso é provavelmente uma boa ideia implementá-lo de qualquer maneira (com algumas precauções).
Referências
Você está usando o Httpclient errado e está desestabilizando seu software
Singleton HttpClient? Cuidado com esse comportamento sério e como corrigi-lo
Padrões e práticas da Microsoft - Otimização de desempenho: instanciação imprópria
Instância única de HttpClient reutilizável na revisão de código
Singleton HttpClient não respeita as alterações de DNS (CoreFX)
Conselho geral sobre o uso de HttpClient
fonte
Estou atrasado para a festa, mas aqui está minha jornada de aprendizado sobre este assunto complicado.
1. Onde podemos encontrar o advogado oficial da reutilização do HttpClient?
Quero dizer, se a reutilização do HttpClient é planejada e isso é importante , esse advogado é melhor documentado em sua própria documentação da API, em vez de estar oculto em muitos "Tópicos avançados", "Padrão (anti) de desempenho" ou outras postagens do blog por aí . Caso contrário, como um novo aluno deve saber isso antes que seja tarde demais?
A partir de agora (maio de 2018), o primeiro resultado de pesquisa ao pesquisar "c # httpclient" aponta para esta página de referência da API no MSDN , que não menciona essa intenção. Bem, a lição 1 aqui para iniciantes é: sempre clique no link "Outras versões" logo após o título da página de ajuda do MSDN, você provavelmente encontrará links para a "versão atual" lá. Nesse caso HttpClient, ele o levará ao documento mais recente aqui, contendo a descrição da intenção .
Suspeito que muitos desenvolvedores que não conheciam esse tópico também não encontraram a página de documentação correta; é por isso que esse conhecimento não é amplamente difundido e as pessoas ficaram surpresas quando descobriram mais tarde , possivelmente da maneira mais difícil .
2. A (mis?) Concepção de
using
IDisposable
Este é um pouco fora de tópico, mas ainda vale ressaltar que, não é uma coincidência ver as pessoas nessas postagens do blog culparem como
HttpClient
aIDisposable
interface faz com que elas usem ousing (var client = new HttpClient()) {...}
padrão e depois levem ao problema.Eu acredito que isso se resume a uma concepção não dita (mis?): "Espera-se que um objeto descartável seja de curta duração" .
NO ENTANTO, embora certamente pareça algo de curta duração quando escrevemos código neste estilo:
a documentação oficial sobre IDisposable nunca menciona que os
IDisposable
objetos precisam ter vida curta. Por definição, o IDisposable é apenas um mecanismo que permite liberar recursos não gerenciados. Nada mais. Nesse sentido, espera-se que você ative o descarte, mas isso não exige que você faça isso de maneira breve.Portanto, é seu trabalho escolher adequadamente quando acionar o descarte, com base nos requisitos do ciclo de vida do seu objeto real. Não há nada que o impeça de usar um IDisposable de uma maneira duradoura:
Com esse novo entendimento, agora que revisitamos a postagem do blog , podemos notar claramente que a "correção" é inicializada
HttpClient
uma vez, mas nunca a elimina, é por isso que podemos ver em sua saída do netstat que a conexão permanece no estado ESTABLISHED, o que significa que NÃO foi fechado corretamente. Se estivesse fechado, seu estado estaria em TIME_WAIT. Na prática, não é grande coisa vazar apenas uma conexão aberta após todo o programa terminar, e o pôster do blog ainda vê um ganho de desempenho após a correção; mas ainda assim, é conceitualmente incorreto culpar o IDisposable e optar por NÃO descartá-lo.3. Temos que colocar o HttpClient em uma propriedade estática ou até colocá-lo como um singleton?
Com base no entendimento da seção anterior, acho que a resposta aqui fica clara: "não necessariamente". Realmente depende de como você organiza seu código, desde que você reutilize um HttpClient AND (idealmente) o descarte eventualmente.
Hilariamente, nem mesmo o exemplo na seção Observações do documento oficial atual é estritamente correto. Ele define uma classe "GoodController", contendo uma propriedade estática HttpClient que não será descartada; que desobedece o que outro exemplo da seção Exemplos enfatiza: "precisa chamar descarte ... para que o aplicativo não vaze recursos".
E, por último, o singleton não deixa de ter seus próprios desafios.
- Citado nesta palestra inspiradora, "Estado Global e Singletons"
PS: SqlConnection
Este é irrelevante para as perguntas e respostas atuais, mas provavelmente é bom saber. O padrão de uso do SqlConnection é diferente. Você NÃO precisa reutilizar o SqlConnection , porque ele manipulará seu pool de conexões melhor dessa maneira.
A diferença é causada por sua abordagem de implementação. Cada instância HttpClient usa seu próprio conjunto de conexões (citado aqui ); mas o próprio SqlConnection é gerenciado por um pool de conexão central, de acordo com isso .
E você ainda precisa descartar o SqlConnection, o mesmo que você deve fazer para o HttpClient.
fonte
Eu fiz alguns testes ver melhorias de desempenho com estática
HttpClient
. Usei o código abaixo para meus testes:Para teste:
Eu encontrei a melhoria de desempenho entre 40% a 60% de uon usando estática em
HttpClient
vez de descartá-la paraHttpClient
solicitação. Coloquei os detalhes do resultado do teste de desempenho na postagem do blog aqui .fonte
Para fechar corretamente a conexão TCP , precisamos concluir uma sequência de pacotes FIN - FIN + ACK - ACK (assim como SYN - SYN + ACK - ACK, ao abrir uma conexão TCP ). Se simplesmente chamarmos o método .Close () (normalmente acontece quando um HttpClient está sendo descartado) e não esperarmos que o lado remoto confirme nossa solicitação de fechamento (com FIN + ACK), terminaremos com o estado TIME_WAIT em a porta TCP local, porque descartamos nosso ouvinte (HttpClient) e nunca tivemos a chance de redefinir o estado da porta para um estado fechado adequado, uma vez que o ponto remoto nos envia o pacote FIN + ACK.
A maneira correta de fechar a conexão TCP seria chamar o método .Close () e aguardar o evento close do outro lado (FIN + ACK) chegar ao nosso lado. Somente então podemos enviar nosso ACK final e descartar o HttpClient.
Apenas para adicionar, faz sentido manter as conexões TCP abertas, se você estiver executando solicitações HTTP, devido ao cabeçalho HTTP "Connection: Keep-Alive". Além disso, você pode pedir ao ponto remoto para fechar a conexão, definindo o cabeçalho HTTP "Conexão: Fechar". Dessa forma, suas portas locais sempre estarão fechadas corretamente, em vez de estarem no estado TIME_WAIT.
fonte
Aqui está um cliente básico da API que usa o HttpClient e HttpClientHandler com eficiência. Quando você cria um novo HttpClient para fazer uma solicitação, há muita sobrecarga. NÃO recrie HttpClient para cada solicitação. Reutilize o HttpClient o máximo possível ...
Uso:
fonte
Não há uma maneira de usar a classe HttpClient. A chave é arquitetar seu aplicativo de uma maneira que faça sentido para seu ambiente e restrições.
O HTTP é um ótimo protocolo para usar quando você precisa expor APIs públicas. Também pode ser usado efetivamente para serviços internos leves e de baixa latência - embora o padrão da fila de mensagens RPC seja frequentemente uma escolha melhor para serviços internos.
Há muita complexidade em se executar bem o HTTP.
Considere o seguinte:
Mas, acima de tudo, teste, meça e confirme. Se não estiver funcionando como planejado, podemos responder a perguntas específicas sobre como alcançar os resultados esperados.
fonte
HttpClient
implementaIDisposable
. Portanto, não é irracional esperar que seja um objeto de vida curta que saiba como limpar a si próprio, adequado para agrupar umausing
declaração toda vez que você precisar. Infelizmente, não é assim que realmente funciona. A postagem do blog que o OP vinculou demonstra claramente que existem recursos (especificamente, conexões de soquete TCP) queusing
permanecem por muito tempo após a declaração ficar fora do escopo e oHttpClient
objeto ter sido descartado.