¿Se deben eliminar HttpClient y HttpClientHandler entre solicitudes?
System.Net.Http.HttpClient y System.Net.Http.HttpClientHandler en .NET Framework 4.5 implementan IDisposable (a través de System.Net.Http.HttpMessageInvoker ).
La using
documentación de la declaración dice:
Como regla general, cuando usa un objeto IDisposable, debe declararlo y crear una instancia del mismo en una declaración de uso.
Esta respuesta usa este patrón:
var baseAddress = new Uri("http://example.com");
var cookieContainer = new CookieContainer();
using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
using (var client = new HttpClient(handler) { BaseAddress = baseAddress })
{
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("foo", "bar"),
new KeyValuePair<string, string>("baz", "bazinga"),
});
cookieContainer.Add(baseAddress, new Cookie("CookieName", "cookie_value"));
var result = client.PostAsync("/test", content).Result;
result.EnsureSuccessStatusCode();
}
Pero los ejemplos más visibles de Microsoft no llaman Dispose()
ni explícita ni implícitamente. Por ejemplo:
- El artículo original del blog que anuncia el lanzamiento de HttpClient.
- La documentación actual de MSDN para HttpClient.
- BingTranslateSample
- Muestra de Google Maps
- Muestra del Banco Mundial
En los comentarios del anuncio , alguien le preguntó al empleado de Microsoft:
Después de revisar sus muestras, vi que no realizó la acción de eliminación en la instancia de HttpClient. He usado todas las instancias de HttpClient con la declaración de uso en mi aplicación y pensé que es la forma correcta ya que HttpClient implementa la interfaz IDisposable. ¿Estoy en el camino correcto?
Su respuesta fue:
En general, eso es correcto, aunque hay que tener cuidado con "using" y async ya que realmente no se mezclan en .Net 4. En .Net 4.5 puedes usar "await" dentro de una declaración "using".
Por cierto, puede reutilizar el mismo HttpClient tantas veces como desee, por lo que normalmente no los creará ni los eliminará todo el tiempo.
El segundo párrafo sobra para esta pregunta, que no se preocupa de cuántas veces puedes usar una instancia de HttpClient, sino de si es necesario desecharla después de que ya no la necesites.
(Actualización: de hecho, ese segundo párrafo es la clave de la respuesta, como lo proporciona @DPeden a continuación).
Entonces mis preguntas son:
¿Es necesario, dada la implementación actual (.NET Framework 4.5), llamar a Dispose() en instancias HttpClient y HttpClientHandler? Aclaración: por "necesario" me refiero a si hay consecuencias negativas por no eliminarlos, como fugas de recursos o riesgos de corrupción de datos.
Si no es necesario, ¿sería una "buena práctica" de todos modos, ya que implementan IDisposable?
Si es necesario (o recomendado), ¿ este código mencionado anteriormente lo implementa de forma segura (para .NET Framework 4.5)?
Si estas clases no requieren llamar a Dispose(), ¿por qué se implementaron como IDisposable?
Si lo requieren, o si es una práctica recomendada, ¿los ejemplos de Microsoft son engañosos o inseguros?
El consenso general es que no es necesario (no debería) deshacerse de HttpClient.
Muchas personas que están íntimamente involucradas en su funcionamiento lo han afirmado.
Consulte la publicación del blog de Darrel Miller y una publicación SO relacionada: El rastreo de HttpClient produce una pérdida de memoria como referencia.
También le sugiero encarecidamente que lea el capítulo HttpClient de Diseño de API web evolucionables con ASP.NET para conocer el contexto de lo que sucede bajo el capó, en particular la sección "Ciclo de vida" que se cita aquí:
Aunque HttpClient implementa indirectamente la interfaz IDisposable, el uso estándar de HttpClient es no deshacerse de ella después de cada solicitud. El objeto HttpClient está diseñado para vivir mientras su aplicación necesite realizar solicitudes HTTP. Tener un objeto existente en múltiples solicitudes habilita un lugar para configurar DefaultRequestHeaders y evita que tenga que volver a especificar cosas como CredentialCache y CookieContainer en cada solicitud como era necesario con HttpWebRequest.
O incluso abrir DotPeek.
Las respuestas actuales son un poco confusas y engañosas, y les faltan algunas implicaciones importantes del DNS. Intentaré resumir claramente dónde están las cosas.
- En términos generales, lo ideal es que la mayoría de
IDisposable
los objetos se eliminen cuando haya terminado con ellos , especialmente aquellos que poseen recursos del sistema operativo con nombre/compartidos .HttpClient
no es una excepción, ya que, como señala Darrel Miller , asigna tokens de cancelación y los cuerpos de solicitud/respuesta pueden ser flujos no administrados. - Sin embargo, la mejor práctica para HttpClient dice que debes crear una instancia y reutilizarla tanto como sea posible (usando sus miembros seguros para subprocesos en escenarios de subprocesos múltiples). Por lo tanto, en la mayoría de los casos nunca lo desecharás simplemente porque lo necesitarás todo el tiempo .
- El problema de reutilizar el mismo HttpClient "para siempre" es que la conexión HTTP subyacente puede permanecer abierta frente a la IP originalmente resuelta por DNS, independientemente de los cambios de DNS . Esto puede ser un problema en escenarios como la implementación azul/verde y la conmutación por error basada en DNS . Existen varios enfoques para abordar este problema, el más confiable implica que el servidor envíe un
Connection:close
encabezado después de que se realizan los cambios de DNS. Otra posibilidad implica reciclar elHttpClient
del lado del cliente, ya sea periódicamente o mediante algún mecanismo que aprenda sobre el cambio de DNS. Consulte https://github.com/dotnet/corefx/issues/11224 para obtener más información (sugiero leerlo detenidamente antes de usar ciegamente el código sugerido en la publicación del blog vinculada).
Como parece que nadie lo ha mencionado aquí todavía, la nueva y mejor manera de administrar HttpClient y HttpClientHandler en .NET Core >=2.1 y .NET 5.0+ es usar HttpClientFactory .
Resuelve la mayoría de los problemas y trampas antes mencionados de una manera limpia y fácil de usar. De la gran publicación del blog de Steve Gordon :
Agregue los siguientes paquetes a su proyecto .Net Core (2.1.1 o posterior):
Microsoft.AspNetCore.All
Microsoft.Extensions.Http
Agregue esto a Startup.cs:
services.AddHttpClient();
Inyectar y utilizar:
[Route("api/[controller]")]
public class ValuesController : Controller
{
private readonly IHttpClientFactory _httpClientFactory;
public ValuesController(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
[HttpGet]
public async Task<ActionResult> Get()
{
var client = _httpClientFactory.CreateClient();
var result = await client.GetStringAsync("http://www.google.com");
return Ok(result);
}
}
Explore la serie de publicaciones en el blog de Steve para conocer muchas más funciones.
Según tengo entendido, la llamada Dispose()
es necesaria solo cuando se bloquean recursos que necesitará más adelante (como una conexión en particular). Siempre se recomienda liberar recursos que ya no estás usando, incluso si no los vuelves a necesitar, simplemente porque generalmente no deberías conservar recursos que no estás usando (juego de palabras).
El ejemplo de Microsoft no es necesariamente incorrecto. Todos los recursos utilizados se liberarán cuando se cierre la aplicación. Y en el caso de ese ejemplo, eso sucede casi inmediatamente después de que se HttpClient
termina de usar. En casos similares, llamar explícitamente Dispose()
es algo superfluo.
Pero, en general, cuando una clase implementa IDisposable
, se entiende que debes Dispose()
hacerlo tan pronto como estés completamente listo y capacitado. Yo diría que esto es particularmente cierto en casos en los HttpClient
que no está documentado explícitamente si los recursos o las conexiones se mantienen o se abren. En el caso de que la conexión se reutilice nuevamente [pronto], querrás renunciar Dipose()
a ella; en ese caso, no estás "completamente listo".
Ver también: Método IDisposable.Dispose y Cuándo llamar a Dispose
Respuesta corta: No, la afirmación de la respuesta actualmente aceptada NO es precisa : "El consenso general es que no es necesario (no debería) deshacerse de HttpClient".
Respuesta larga : AMBAS de las siguientes afirmaciones son verdaderas y alcanzables al mismo tiempo:
- "HttpClient está diseñado para crear una instancia una vez y reutilizarse durante toda la vida de una aplicación", citado en la documentación oficial .
IDisposable
Se supone/recomienda eliminar un objeto.
Y NO NECESARIAMENTE ENTRAN EN CONFLICTO entre sí. Es solo una cuestión de cómo organizas tu código para reutilizarlo HttpClient
Y aun así desecharlo correctamente.
Una respuesta aún más larga citada de mi otra respuesta :
No es una coincidencia ver personas en algunas publicaciones de blogs culpando a cómo HttpClient
la IDisposable
interfaz les hace tender a usar el using (var client = new HttpClient()) {...}
patrón y luego conducir a un problema de manejo de sockets agotado.
Creo que eso se reduce a una concepción tácita (¿errónea?): "Se espera que un objeto IDisposable tenga una vida corta" .
SIN EMBARGO, si bien ciertamente parece algo de corta duración cuando escribimos código en este estilo:
using (var foo = new SomeDisposableObject())
{
...
}
la documentación oficial sobre IDisposable
nunca menciona que IDisposable
los objetos tengan que ser de corta duración. Por definición, IDisposable es simplemente un mecanismo que le permite liberar recursos no administrados. Nada mas. En ese sentido, se ESPERA que usted eventualmente active la eliminación, pero no requiere que lo haga de manera breve.
Por lo tanto, es su trabajo elegir adecuadamente cuándo activar la eliminación, en función de los requisitos del ciclo de vida real de su objeto. No hay nada que le impida utilizar un IDisposable de forma duradera:
using System;
namespace HelloWorld
{
class Hello
{
static void Main()
{
Console.WriteLine("Hello World!");
using (var client = new HttpClient())
{
for (...) { ... } // A really long loop
// Or you may even somehow start a daemon here
}
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
}
Con este nuevo entendimiento, ahora volvemos a visitar esa publicación de blog , podemos notar claramente que la "solución" se inicializa HttpClient
una vez pero nunca la elimina, es por eso que podemos ver en su salida de netstat que la conexión permanece en el estado ESTABLECIDO, lo que significa que tiene NO ha sido cerrado correctamente. Si estuviera cerrado, su estado estaría en TIME_WAIT. En la práctica, no es gran cosa dejar abierta solo una conexión después de que finaliza todo el programa, y el cartel del blog aún ve una mejora en el rendimiento después de la solución; pero aún así, es conceptualmente incorrecto culpar a IDisposable y optar por NO desecharlo.