Hace poco me encontré con esta publicación de blog de monstruos asp.net que habla sobre problemas con el uso HttpClient
de la siguiente manera:
using(var client = new HttpClient())
{
}
Según la publicación del blog, si desechamos el HttpClient
después de cada solicitud, puede mantener abiertas las conexiones TCP. Esto puede conducir potencialmente a System.Net.Sockets.SocketException
.
La forma correcta según la publicación es crear una sola instancia, HttpClient
ya que ayuda a reducir el desperdicio de enchufes.
De la publicación:
Si compartimos una sola instancia de HttpClient, podemos reducir el desperdicio de sockets reutilizándolos:
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(); } } }
Siempre he dispuesto el HttpClient
objeto después de usarlo, ya que considero que esta es la mejor manera de usarlo. Pero esta publicación de blog ahora me hace sentir que lo estaba haciendo mal todo este tiempo.
¿Deberíamos crear una nueva instancia única HttpClient
para todas las solicitudes? ¿Hay alguna dificultad al usar la instancia estática?
fuente
Close()
o inicie un nuevoGet()
. Si acaba de deshacerse del cliente cuando haya terminado con él, no habrá nadie para manejar ese apretón de manos de cierre y todos sus puertos tendrán el estado TIME_WAIT, por eso.Respuestas:
Parece una publicación de blog convincente. Sin embargo, antes de tomar una decisión, primero realizaría las mismas pruebas que realizó el escritor del blog, pero en su propio código. También intentaría descubrir un poco más sobre HttpClient y su comportamiento.
Esta publicación dice:
Entonces, lo que probablemente sucede cuando se comparte un HttpClient es que las conexiones se están reutilizando, lo cual está bien si no necesita conexiones persistentes. La única forma de saber con certeza si esto es importante para su situación es ejecutar sus propias pruebas de rendimiento.
Si cava, encontrará varios otros recursos que abordan este problema (incluido un artículo sobre las mejores prácticas de Microsoft), por lo que probablemente sea una buena idea implementar de todos modos (con algunas precauciones).
Referencias
¿Está utilizando Httpclient incorrecto y está desestabilizando su software
Singleton HttpClient? Tenga cuidado con este comportamiento grave y cómo solucionarlo
Patrones y prácticas de Microsoft - Optimización del rendimiento: instanciación incorrecta Instancia
única de HttpClient reutilizable en la revisión de código
Singleton HttpClient no respeta los cambios de DNS (CoreFX)
Consejos generales para usar HttpClient
fuente
Llego tarde a la fiesta, pero aquí está mi viaje de aprendizaje sobre este tema complicado.
1. ¿Dónde podemos encontrar al defensor oficial sobre la reutilización de HttpClient?
Quiero decir, si se pretende reutilizar HttpClient y hacerlo es importante , dicho defensor está mejor documentado en su propia documentación API, en lugar de estar oculto en muchos "Temas avanzados", "Patrón de rendimiento (anti)" u otras publicaciones de blog. . De lo contrario, ¿cómo se supone que un nuevo alumno debe saberlo antes de que sea demasiado tarde?
A partir de ahora (mayo de 2018), el primer resultado de búsqueda al buscar en Google "c # httpclient" apunta a esta página de referencia de API en MSDN , que no menciona esa intención en absoluto. Bueno, la lección 1 aquí para los novatos es, siempre haga clic en el enlace "Otras versiones" justo después del título de la página de ayuda de MSDN, probablemente encontrará enlaces a la "versión actual" allí. En este caso de HttpClient, lo llevará al último documento que contiene esa descripción de intención .
Sospecho que muchos desarrolladores que eran nuevos en este tema tampoco encontraron la página de documentación correcta, es por eso que este conocimiento no está muy extendido, y la gente se sorprendió cuando lo descubrieron más tarde , posiblemente de una manera difícil .
2. La concepción (¿equivocada?) De
using
IDisposable
Este es un poco fuera de tema, pero aún vale la pena señalar que, no es una coincidencia ver a las personas en esas publicaciones de blog mencionadas culpando cómo
HttpClient
laIDisposable
interfaz hace que tienden a usar elusing (var client = new HttpClient()) {...}
patrón y luego conducen al problema.Creo que eso se reduce a una concepción tácita (¿equivocada?): "Se espera que un objeto IDisposable sea de corta duración" .
SIN EMBARGO, aunque ciertamente parece algo de corta duración cuando escribimos código en este estilo:
La documentación oficial sobre IDisposable nunca menciona que los
IDisposable
objetos tienen que ser de corta duración. Por definición, IDisposable es simplemente un mecanismo para permitirle liberar recursos no administrados. Nada mas. En ese sentido, se ESPERA que eventualmente active la eliminación, pero no requiere que lo haga de manera efímera.Por lo tanto, es su trabajo elegir adecuadamente cuándo activar la eliminación, basándose en el requisito del ciclo de vida de su objeto real. No hay nada que le impida usar un ID desechable de una manera duradera:
Con esta nueva comprensión, ahora que volvemos a visitar esa publicación de blog , podemos notar claramente que la "corrección" se inicializa
HttpClient
una vez, pero nunca la elimina, por eso podemos ver en su salida de netstat que, la conexión permanece en estado ESTABLECIDO, lo que significa que tiene NO se ha cerrado correctamente. Si estuviera cerrado, su estado estaría en TIME_WAIT en su lugar. En la práctica, no es un gran problema filtrar solo una conexión abierta después de que termine todo el programa, y el póster del blog aún ve una ganancia de rendimiento después de la solución; pero aún así, es conceptualmente incorrecto culpar a IDisposable y elegir NO desecharlo.3. ¿Tenemos que poner HttpClient en una propiedad estática, o incluso ponerlo como un singleton?
Basado en la comprensión de la sección anterior, creo que la respuesta aquí se vuelve clara: "no necesariamente". Realmente depende de cómo organice su código, siempre que reutilice un HttpClient Y (idealmente) lo elimine eventualmente.
Hilarantemente, ni siquiera el ejemplo en la sección de Comentarios del documento oficial actual lo hace estrictamente correcto. Define una clase "GoodController", que contiene una propiedad HttpClient estática que no se eliminará; que desobedece lo que otro ejemplo en la sección de Ejemplos enfatiza: "necesita llamar a disponer ... para que la aplicación no pierda recursos".
Y, por último, el singleton no está exento de desafíos propios.
- Citado de esta charla inspiradora, "Global State and Singletons"
PS: SqlConnection
Este es irrelevante para las preguntas y respuestas actuales, pero probablemente sea bueno saberlo. El patrón de uso de SqlConnection es diferente. Usted NO tiene que volver a utilizar SqlConnection , ya que se encargará de su grupo de conexión mejor de esa manera.
La diferencia es causada por su enfoque de implementación. Cada instancia de HttpClient usa su propio grupo de conexiones (citado desde aquí ); pero SqlConnection en sí es administrado por un grupo de conexiones central, de acuerdo con esto .
Y aún debe deshacerse de SqlConnection, igual que se supone que debe hacer para HttpClient.
fuente
Hice algunas pruebas para ver mejoras de rendimiento con estática
HttpClient
. Usé el siguiente código para mis pruebas:Para las pruebas:
Encontré la mejora del rendimiento entre el 40% y el 60% usando estática en
HttpClient
lugar de desecharla paraHttpClient
solicitarla. He puesto los detalles del resultado de la prueba de rendimiento en la publicación del blog aquí .fuente
Para cerrar correctamente la conexión TCP , debemos completar una secuencia de paquetes FIN - FIN + ACK - ACK (al igual que SYN - SYN + ACK - ACK, al abrir una conexión TCP ). Si solo llamamos a un método .Close () (generalmente ocurre cuando un HttpClient está desechando), y no esperamos que el lado remoto confirme nuestra solicitud de cierre (con FIN + ACK), terminamos con el estado TIME_WAIT en el puerto TCP local, porque desechamos nuestro oyente (HttpClient) y nunca tuvimos la oportunidad de restablecer el estado del puerto a un estado cerrado adecuado, una vez que el par remoto nos envía el paquete FIN + ACK.
La forma correcta de cerrar la conexión TCP sería llamar al método .Close () y esperar a que el evento de cierre del otro lado (FIN + ACK) llegue de nuestro lado. Solo entonces podemos enviar nuestro ACK final y eliminar el HttpClient.
Solo para agregar, tiene sentido mantener abiertas las conexiones TCP, si está realizando solicitudes HTTP, debido al encabezado HTTP "Conexión: Keep-Alive". Además, puede solicitar al par remoto que cierre la conexión por usted, en su lugar, estableciendo el encabezado HTTP "Conexión: Cerrar". De esa manera, sus puertos locales siempre estarán cerrados correctamente, en lugar de estar en un estado TIME_WAIT.
fuente
Aquí hay un cliente API básico que utiliza HttpClient y HttpClientHandler de manera eficiente. Cuando crea un nuevo HttpClient para realizar una solicitud, hay muchos gastos generales. NO vuelva a crear HttpClient para cada solicitud. Reutilice HttpClient tanto como sea posible ...
Uso:
fuente
No hay una sola forma de usar la clase HttpClient. La clave es diseñar su aplicación de una manera que tenga sentido para su entorno y sus limitaciones.
HTTP es un gran protocolo para usar cuando necesita exponer API públicas. También se puede usar de manera efectiva para servicios internos de baja latencia y bajo peso, aunque el patrón de la cola de mensajes RPC suele ser una mejor opción para los servicios internos.
Hay mucha complejidad en hacer HTTP bien.
Considera lo siguiente:
Pero, sobre todo, pruebe, mida y confirme. Si no se comporta como se diseñó, entonces podemos responder preguntas específicas sobre cómo lograr los resultados esperados.
fuente
HttpClient
implementosIDisposable
. Por lo tanto, no es irrazonable esperar que sea un objeto de corta duración que sepa cómo limpiarse por sí mismo, adecuado para envolverlo en unausing
declaración cada vez que lo necesite. Desafortunadamente, no es así como funciona realmente. La publicación de blog que el OP enlazó claramente demuestra que hay recursos (específicamente, conexiones de socket TCP) que viven mucho después de que lausing
declaración haya salido del alcance y elHttpClient
objeto presumiblemente haya sido eliminado.