¿Deben eliminarse HttpClient y HttpClientHandler?

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 documentación de la statement de using dice:

Como regla general, cuando utiliza un objeto IDisposable, debe declararlo y crear una instancia en una instrucción using.

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("foo", "bar"), new KeyValuePair("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 a Dispose() explícita o implícitamente. Por ejemplo:

  • El artículo original del blog que anuncia la distribución de HttpClient.
  • La documentación real de MSDN para HttpClient.
  • BingTranslateSample
  • GoogleMapsSample
  • WorldBankSample

En los comentarios del anuncio , alguien le preguntó al empleado de Microsoft:

Después de verificar sus muestras, vi que no realizó la acción de eliminación en la instancia de HttpClient. He utilizado todas las instancias de HttpClient con el uso de la statement en mi aplicación y pensé que era la forma correcta desde HttpClient implementa la interfaz IDisposable. ¿Estoy en el camino correcto?

Su respuesta fue:

En general, eso es correcto, aunque hay que tener cuidado con “usar” y asincronizar ya que no se mezclan realmente en .Net 4, en .Net 4.5 puede usar “esperar” dentro de una instrucción “usar”.

Por cierto, puede reutilizar el mismo HttpClient tantas veces como quiera, por lo que normalmente no las creará / desechará todo el tiempo.

El segundo párrafo es superfluo a esta pregunta, que no le preocupa cuántas veces puede usar una instancia de HttpClient, pero sí si es necesario desecharla una vez que ya no la necesita.

(Actualización: de hecho, ese segundo párrafo es la clave de la respuesta, como se indica a continuación por @DPeden).

Entonces mis preguntas son:

  1. ¿Es necesario, dada la implementación actual (.NET Framework 4.5), invocar Dispose () en las instancias HttpClient y HttpClientHandler? Aclaración: por “necesario” me refiero a si hay consecuencias negativas por no eliminar, como la fuga de recursos o los riesgos de corrupción de datos.

  2. Si no es necesario, ¿sería una “buena práctica” de todos modos, ya que implementan IDisposable?

  3. Si es necesario (o recomendado), ¿ este código se menciona anteriormente y lo está implementando de forma segura (para .NET Framework 4.5)?

  4. Si estas clases no requieren llamar a Dispose (), ¿por qué se implementaron como IDisposable?

  5. 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 debe (no debería) necesitar deshacerse de HttpClient.

Muchas personas que están íntimamente involucradas en la forma en que funciona han declarado esto.

Consulte la publicación de blog de Darrel Miller y una publicación de SO relacionada: el rastreo de HttpClient provoca una pérdida de memoria como referencia.

También le sugiero encarecidamente que lea el capítulo HttpClient de Designing Evolvable Web APIs con ASP.NET para conocer el contexto de lo que está sucediendo bajo el capó, particularmente la sección “Ciclo de vida” que se cita aquí:

Aunque HttpClient implementa indirectamente la interfaz IDisposable, el uso estándar de HttpClient no consiste en eliminarlo después de cada solicitud. El objeto HttpClient está destinado a vivir mientras su aplicación necesite realizar solicitudes HTTP. Tener un objeto en varias solicitudes habilita un lugar para establecer DefaultRequestHeaders y evita tener que volver a especificar cosas como CredentialCache y CookieContainer en cada solicitud, como era necesario con HttpWebRequest.

O incluso abre DotPeek.

Las respuestas actuales son un poco confusas y engañosas, y les faltan algunas implicaciones importantes del DNS. Trataré de resumir dónde están las cosas claramente.

  1. En términos generales, la mayoría de los objetos IDisposable deberían desecharse cuando haya terminado con ellos , especialmente aquellos que poseen recursos de sistema operativo con nombre / compartidos . HttpClient no es una excepción, ya que, como Darrel Miller señala, asigna tokens de cancelación y los cuerpos de solicitud / respuesta pueden ser flujos no administrados.
  2. Sin embargo, la mejor práctica para HttpClient dice que debe 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 escenarios , nunca se deshará de él simplemente porque lo necesitará todo el tiempo .
  3. El problema con la reutilización del mismo HttpClient “para siempre” es que la conexión HTTP subyacente puede permanecer abierta frente a la IP resuelta originalmente por el DNS, independientemente de los cambios en el DNS . Esto puede ser un problema en escenarios como el despliegue azul / verde y el failover basado en DNS . Hay varios enfoques para tratar este problema, el más confiable que implica que el servidor envíe una Connection:close encabezado después de que se produzcan cambios en el DNS. Otra posibilidad implica reciclar el HttpClient en el lado del cliente, ya sea periódicamente o mediante algún mecanismo que aprenda sobre el cambio del DNS. Consulte https://github.com/dotnet/corefx/issues/11224 para obtener más información (sugiero leerla detenidamente antes de usar ciegamente el código sugerido en la publicación de blog vinculada).

En mi entender, llamar a Dispose() es necesario solo cuando bloquea los recursos que necesita más adelante (como una conexión en particular). Siempre se recomienda liberar recursos que ya no usas, incluso si no los necesitas de nuevo, simplemente porque en general no deberías retener recursos que no estás usando (juego de palabras).

El ejemplo de Microsoft no es incorrecto, necesariamente. Todos los recursos utilizados se liberarán cuando la aplicación finalice. Y en el caso de ese ejemplo, eso ocurre casi inmediatamente después de que HttpClient usarse. En casos similares, llamar explícitamente a Dispose() es algo superfluo.

Pero, en general, cuando una clase implementa IDisposable , el entendimiento es que debe Dispose() de sus instancias tan pronto como esté listo y habilitado. Postularía que esto es particularmente cierto en casos como HttpClient en el que no está documentado explícitamente si los recursos o las conexiones se mantienen o abren. En el caso en que la conexión se volverá a utilizar [pronto], querrá renunciar a Dipose() – no está “completamente listo” en ese caso.

Ver también: IDisposable. Método de eliminación y cuándo llamar Eliminar

Dispose () llama al código siguiente, que cierra las conexiones abiertas por la instancia de HttpClient. El código fue creado descomstackndo con dotPeek.

HttpClientHandler.cs – Eliminar

 ServicePointManager.CloseConnectionGroups(this.connectionGroupName); 

Si no llama a dispose, ServicePointManager.MaxServicePointIdleTime, que se ejecuta con un temporizador, cerrará las conexiones http. El valor predeterminado es 100 segundos.

ServicePointManager.cs

 internal static readonly TimerThread.Callback s_IdleServicePointTimeoutDelegate = new TimerThread.Callback(ServicePointManager.IdleServicePointTimeoutCallback); private static volatile TimerThread.Queue s_ServicePointIdlingQueue = TimerThread.GetOrCreateQueue(100000); private static void IdleServicePointTimeoutCallback(TimerThread.Timer timer, int timeNoticed, object context) { ServicePoint servicePoint = (ServicePoint) context; if (Logging.On) Logging.PrintInfo(Logging.Web, SR.GetString("net_log_closed_idle", (object) "ServicePoint", (object) servicePoint.GetHashCode())); lock (ServicePointManager.s_ServicePointTable) ServicePointManager.s_ServicePointTable.Remove((object) servicePoint.LookupString); servicePoint.ReleaseAllConnectionGroups(); } 

Si no ha configurado el tiempo de inactividad en infinito, entonces parece seguro no invocar a deshacerse y dejar que el temporizador de conexión inactiva se active y cerrar las conexiones por usted, aunque sería mejor llamar a disponer de una statement de uso si usted sabe que ha terminado con una instancia de HttpClient y libera los recursos más rápido.

En mi caso, estaba creando un HttpClient dentro de un método que realmente hacía la llamada de servicio. Algo como:

 public void DoServiceCall() { var client = new HttpClient(); await client.PostAsync(); } 

En una función de trabajador de Azure, después de llamar repetidamente a este método (sin eliminar el HttpClient), eventualmente fallaría con SocketException (bash de conexión fallido).

Hice que HttpClient fuera una variable de instancia (eliminándolo en el nivel de clase) y el problema desapareció. Entonces, diría que sí, deshágase del HttpClient, suponiendo que es seguro (no tiene llamadas asincrónicas sobresalientes) para hacerlo.

En el uso típico (respuestas <2 GB), no es necesario desechar los HttpResponseMessages.

Los tipos de devolución de los métodos HttpClient deberían estar Disposed si su Stream Content no está completamente Read. De lo contrario, no hay forma de que CLR sepa que esos flujos se pueden cerrar hasta que sean recolectados.

  • Si está leyendo los datos en un byte [] (por ejemplo, GetByteArrayAsync) o una cadena, todos los datos se leen, por lo que no es necesario desecharlos.
  • Las demás sobrecargas tomarán de forma predeterminada la lectura del flujo de hasta 2GB (HttpCompletionOption es ResponseContentRead, HttpClient.MaxResponseContentBufferSize por defecto es de 2GB)

Si configura HttpCompletionOption para ResponseHeadersRead o la respuesta es mayor a 2GB, debe limpiar. Esto se puede hacer llamando a Dispose en HttpResponseMessage o llamando a Dispose / Close en el Stream obtenido del contenido de HttpResonseMessage o leyendo el contenido por completo.

Si llama a Dispose en el HttpClient depende de si desea cancelar solicitudes pendientes o no.

Si desea deshacerse de HttpClient, puede hacerlo si lo configura como un grupo de recursos. Y al final de su aplicación, dispone de su fondo de recursos.

Código:

 // Notice that IDisposable is not implemented here! public interface HttpClientHandle { HttpRequestHeaders DefaultRequestHeaders { get; } Uri BaseAddress { get; set; } // ... // All the other methods from peeking at HttpClient } public class HttpClientHander : HttpClient, HttpClientHandle, IDisposable { public static ConditionalWeakTable _httpClientsPool; public static HashSet _uris; static HttpClientHander() { _httpClientsPool = new ConditionalWeakTable(); _uris = new HashSet(); SetupGlobalPoolFinalizer(); } private DateTime _delayFinalization = DateTime.MinValue; private bool _isDisposed = false; public static HttpClientHandle GetHttpClientHandle(Uri baseUrl) { HttpClientHander httpClient = _httpClientsPool.GetOrCreateValue(baseUrl); _uris.Add(baseUrl); httpClient._delayFinalization = DateTime.MinValue; httpClient.BaseAddress = baseUrl; return httpClient; } void IDisposable.Dispose() { _isDisposed = true; GC.SuppressFinalize(this); base.Dispose(); } ~HttpClientHander() { if (_delayFinalization == DateTime.MinValue) _delayFinalization = DateTime.UtcNow; if (DateTime.UtcNow.Subtract(_delayFinalization) < base.Timeout) GC.ReRegisterForFinalize(this); } private static void SetupGlobalPoolFinalizer() { AppDomain.CurrentDomain.ProcessExit += (sender, eventArgs) => { FinalizeGlobalPool(); }; } private static void FinalizeGlobalPool() { foreach (var key in _uris) { HttpClientHander value = null; if (_httpClientsPool.TryGetValue(key, out value)) try { value.Dispose(); } catch { } } _uris.Clear(); _httpClientsPool = null; } } 

var handler = HttpClientHander.GetHttpClientHandle (nuevo Uri (“base url”)).

  • HttpClient, como interfaz, no puede llamar a Dispose ().
  • El Garbage Collector llamará a Dispose () de forma diferida. O cuando el progtwig limpia el objeto a través de su destructor.
  • Utiliza Referencias Débiles + lógica de limpieza demorada para que permanezca en uso siempre que se reutilice con frecuencia.
  • Solo asigna un nuevo HttpClient para cada URL base que se le pasa. Razones explicadas por Ohad Schneider respuesta a continuación. Mal comportamiento al cambiar la URL base.
  • HttpClientHandle permite burlarse en las pruebas

El uso de la dependency injections en su constructor hace que la gestión de la vida útil de su HttpClient sea ​​más fácil, lo que le quita el administrador de por vida fuera del código que lo necesita y hace que sea fácilmente modificable en una fecha posterior.

Mi preferencia actual es crear una clase de cliente http independiente que herede de HttpClient una vez por cada dominio de punto final objective y luego convertirlo en un singleton usando la dependency injection. public class ExampleHttpClient : HttpClient { ... }

Luego tomo una dependencia de constructor en el cliente http personalizado en las clases de servicio donde necesito acceder a esa API. Esto resuelve el problema de la duración y tiene ventajas cuando se trata de la agrupación de conexiones.

Puede ver un ejemplo trabajado en respuesta relacionada en https://stackoverflow.com/a/50238944/3140853

Respuesta corta: No, la statement en la respuesta actualmente aceptada NO es precisa : “El consenso general es que usted no debe (no debería) tener que deshacerse de HttpClient”.

Respuesta larga : AMBAS de las siguientes declaraciones son verdaderas y realizables al mismo tiempo:

  1. “HttpClient está destinado a ser instanciado una vez y reutilizado a lo largo de la vida de una aplicación”, citado en la documentación oficial .
  2. Se supone / recomienda que se IDisposable objeto IDisposable .

Y NO CONFLICTEN NECESARIAMENTE entre sí. Es solo una cuestión de cómo organiza su código para reutilizar un HttpClient Y aún así disponerlo correctamente.

Una respuesta incluso más larga citada de mi otra respuesta :

No es una coincidencia ver a personas en algunas publicaciones de blogs culpando a HttpClient por la interfaz IDisposable que las hace tender a usar el patrón using (var client = new HttpClient()) {...} y luego a agotar el problema del manejador de socket.

Creo que eso se reduce a una concepción tácita (¿no?): “Se espera que un objeto identificable sea efímero” .

SIN EMBARGO, aunque ciertamente parece una cosa efímera cuando escribimos código en este estilo:

 using (var foo = new SomeDisposableObject()) { ... } 

la documentación oficial sobre IDisposable nunca menciona objetos IDisposable deben ser IDisposable . Por definición, IDisposable es simplemente un mecanismo que le permite 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 correctamente cuándo desencadenar la eliminación, de acuerdo con el requisito del ciclo de vida de su objeto real. No hay nada que te impida usar un IDisposable de una manera 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 que volvemos a visitar esa publicación de blog , podemos observar claramente que la “solución” inicializa HttpClient una vez, pero nunca la elimina, es por eso que podemos ver en su salida netstat que la conexión permanece en estado ESTABLECIDO, lo que significa NO ha sido cerrado correctamente. Si estuviera cerrado, su estado sería en TIME_WAIT en su lugar. En la práctica, no es gran problema que solo se filtre una conexión después de que termine todo el progtwig, y ​​el póster del blog aún vea un aumento en el rendimiento después de la corrección; pero aún así, es conceptualmente incorrecto culpar a IDisposable y optar por NO disponerlo.

Creo que uno debe usar el patrón singleton para evitar tener que crear instancias del HttpClient y cerrarlo todo el tiempo. Si usa .Net 4.0, podría usar un código de muestra como se muestra a continuación. Para obtener más información sobre el patrón de singleton, consulte aquí .

 class HttpClientSingletonWrapper : HttpClient { private static readonly Lazy Lazy= new Lazy(()=>new HttpClientSingletonWrapper()); public static HttpClientSingletonWrapper Instance {get { return Lazy.Value; }} private HttpClientSingletonWrapper() { } } 

Usa el código como abajo.

 var client = HttpClientSingletonWrapper.Instance;