¿Tienen que eliminarse HttpClient y HttpClientHandler?


Sistema.Net.Http.HttpClient y Sistema.Net.Http.HttpClientHandler en .NET Framework 4.5 implementar IDisposable (a través de Sistema.Net.Http.HttpMessageInvoker).

La documentación de la declaración using dice:

Como regla general, cuando se utiliza un objeto identificable, debe declarar y instanciarlo en una instrucción using.

Esta respuesta utiliza 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 llame a Dispose() explícita o implícitamente. Por ejemplo:

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

Después de comprobar sus muestras, Vi que no realizaste la eliminación acción en la instancia HttpClient. He utilizado todas las instancias de HttpClient con el uso de la declaración en mi aplicación y pensé que es la manera correcta desde HttpClient implementa la interfaz identificable. Estoy en el camino correcto?

Su respuesta fue:

En general eso es correcto aunque hay que tener cuidado con "usando" y async ya que no ' realmente mezclar en. Net 4, En. Net 4.5 usted puede usar "await" dentro de un "usando" instrucción.

Por cierto, puede reutilizar el mismo HttpClient tantas veces como desee por lo general, usted no va a crear/disponer de ellos todo el tiempo.

El segundo párrafo es superfluo para esta pregunta, que no se refiere a cuántas veces puede usar una instancia HttpClient, sino a si es necesario eliminarla después de que ya no la necesite.

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

Así que mis preguntas son:

  1. ¿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 disponer, 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 implementar IDisposable?

  3. Si es necesario (o recomendado), es este código mencionado anteriormente implementándolo de forma segura (para. NET Framework 4.5)?

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

  5. Si lo requieren, o si es una práctica recomendada, ¿los ejemplos de Microsoft son engañosos o inseguros?

Author: robbie fan, 2013-03-29

11 answers

El consenso general es que no necesita (no debería) deshacerse de HttpClient.

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

Ver La entrada del blog de Darrel Millery una entrada relacionada con SO: El rastreo de HttpClient da como resultado una fuga de memoria para referencia.

También le sugiero encarecidamente que lea el capítulo HttpClient de Diseñando API web evolutivas con ASP.NET para el contexto de lo que está pasando bajo el capó, particularmente la sección "Ciclo de vida" citada aquí:

Aunque HttpClient implemente indirectamente el IDisposable interfaz, el uso estándar de HttpClient no es disponer de ella después de cada petición. El objeto HttpClient está pensado para vivir como siempre que su aplicación necesite realizar solicitudes HTTP. Tener un objeto existir a través de múltiples solicitudes habilita un lugar para establecer 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.

 201
Author: David Peden,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:35

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

  1. En términos generales, la mayoría de los IDisposable objetos idealmente deberían ser eliminados cuando haya terminado con ellos, especialmente aquellos que poseen recursos de SO con nombre/compartidos. HttpClient no es una excepción, ya que como Darrel Miller señala que asigna tokens de cancelación, y los cuerpos de solicitud/respuesta pueden ser flujos no administrados.
  2. Sin embargo, la práctica recomendada de para HttpClient dice que debe crear una instancia y reutilizarla tanto como sea posible (utilizando sus miembros seguros para subprocesos en escenarios multiproceso). Por lo tanto, en la mayoría de los escenarios nunca te desharás de él simplemente porque lo necesitarás todo el tiempo.
  3. El problema con la reutilización del mismo HttpClient "forever" es que la conexión HTTP subyacente podría permanecer abierta contra el IP resuelta por DNS, independientemente de los cambios de DNS . Esto puede ser un problema en escenarios como implementación azul/verde y conmutación por error basada en DNS. Hay varios enfoques para hacer frente a este problema, el más confiable que implica que el servidor envíe un encabezado Connection:close después de que se produzcan cambios en el DNS. Otra posibilidad consiste en reciclar el HttpClient en el lado del cliente, ya sea periódicamente o a través de algún mecanismo que aprenda sobre el cambio de DNS. Ver https://github.com/dotnet/corefx/issues/11224 para más información (sugiero leerlo cuidadosamente antes de usar ciegamente el código sugerido en la entrada del blog enlazada).
 21
Author: Ohad Schneider,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-08 10:21:21

A mi entender, llamar a Dispose() solo es necesario cuando se trata de bloquear los recursos que necesita más adelante (como una conexión en particular). Siempre es recomendadoliberar recursos que ya no estás usando, incluso si no los necesitas de nuevo, simplemente porque no deberías generalmente 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 se cierre la aplicación. Y en el caso de ese ejemplo, que sucede casi inmediatamente después de que el HttpClient se hace siendo utilizado. 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é completamente listo y capaz. Yo postularía que esto es particularmente cierto en casos como HttpClient en los que no está explícitamente documentado en cuanto a si los recursos o conexiones están retenidos/abiertos. En el caso en que el la conexión será reutilizada de nuevo[pronto], querrás renunciar Dipose()a ella {no estás "completamente listo" en ese caso.

Véase también: Identificable.Método Dispose y Cuándo llamar a Dispose

 16
Author: svidgen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-10 15:31:11

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

HttpClientHandler.cs-Dispose

ServicePointManager.CloseConnectionGroups(this.connectionGroupName);

Si no llamas a dispose, entonces ServicePointManager.MaxServicePointIdleTime, que se ejecuta mediante 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 establecido el tiempo de inactividad en infinito, entonces parece seguro no hacerlo llame a dispose y deje que el temporizador de conexión inactiva se active y cierre las conexiones por usted, aunque sería mejor para usted llamar a dispose en una instrucción using si sabe que ha terminado con una instancia HttpClient y libera los recursos más rápido.

 7
Author: Timothy Gonzalez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-26 22:49:51

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

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

En un rol de trabajo de Azure, después de llamar repetidamente a este método (sin disponer del HttpClient), eventualmente fallaría con SocketException (el intento de conexión falló).

Hice el HttpClient una variable de instancia (disponiéndolo a nivel de clase) y el problema desapareció. Así que diría, sí, deseche el HttpClient, asumiendo que es seguro (no tiene llamadas asincrónicas sobresalientes) para hacerlo.

 4
Author: David Faivre,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-26 14:06:31

En el uso típico (respuestas

Los tipos de retorno de los métodos HttpClient deben Eliminarse si su Contenido de flujo no se lee completamente. De lo contrario, no hay manera de que el CLR sepa que esas corrientes pueden cerrarse hasta que se recolecten basura.

  • Si está leyendo los datos en un byte[] (por ejemplo, GetByteArrayAsync) o cadena, todos los datos se leen, por lo que no hay necesidad de disponer.
  • Las otras sobrecargas por defecto a la lectura de la secuencia de hasta 2 GB (HttpCompletionOption es ResponseContentRead, HttpClient.El valor predeterminado de MaxResponseContentBufferSize es 2GB)

Si configura HttpCompletionOption a ResponseHeadersRead o la respuesta es mayor de 2 GB, debe limpiar. Esto se puede hacer llamando a Dispose en el HttpResponseMessage o llamando a Dispose/Close en la Secuencia obtenida del Contenido de HttpResonseMessage o leyendo el contenido completamente.

Si llama Disponer en HttpClient depende de si desea cancelar las solicitudes pendientes o no.

 3
Author: Tom Deseyn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-26 21:40:51

Si desea deshacerse de HttpClient, puede hacerlo si lo configura como un grupo de recursos. Y al final de su aplicación, usted dispone de su grupo 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<Uri, HttpClientHander> _httpClientsPool;
    public static HashSet<Uri> _uris;

    static HttpClientHander()
    {
        _httpClientsPool = new ConditionalWeakTable<Uri, HttpClientHander>();
        _uris = new HashSet<Uri>();
        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 (nueva Uri ("url base")).

  • HttpClient, como interfaz, no puede llamar a Dispose().
  • Dispose() será llamada de forma retardada por el Recolector de Basura. O cuando el programa limpia el objeto a través de su destructor.
  • Usos Referencias débiles + lógica de limpieza retrasada para que permanezca en uso mientras se reutilice con frecuencia.
  • Solo asigna un nuevo HttpClient para cada URL base que se le pasa. Razones explicadas por Ohad Schneider responder a continuación. Mal comportamiento al cambiar la url base.
  • HttpClientHandle permite burlarse en las pruebas
 0
Author: TamusJRoyce,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-27 15:37:15

El uso de la inyección de dependencias en su constructor hace que administrar la vida útil de su HttpClient sea más fácil - tomando el gestor de vida fuera del código que lo necesita y haciéndolo fácilmente cambiable en una fecha posterior.

Mi preferencia actual es crear una clase de cliente http separada que hereda de HttpClient una vez por dominio de punto final de destino y luego convertirlo en un singleton usando inyección de dependencias. public class ExampleHttpClient : HttpClient { ... }

Entonces tomo una dependencia del constructor en el cliente http personalizado en las clases de servicio donde necesito acceso a esa API. Esto resuelve el problema de la vida útil y tiene ventajas cuando se trata de la agrupación de conexiones.

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

 0
Author: alastairtree,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-08 17:37:16

Respuesta corta: No, la afirmación en la respuesta actualmente aceptada NO es exacta: "El consenso general es que no necesita (no debería) deshacerse de HttpClient".

Respuesta larga: LAS dos afirmaciones siguientes son verdaderas y alcanzables al mismo tiempo:{[12]]}

  1. "HttpClient está destinado a ser instanciado una vez y reutilizado durante toda la vida de una aplicación", citado de documentación oficial.
  2. Un objeto IDisposable se supone / se recomienda desechar.

Y NO NECESARIAMENTE ENTRAN en CONFLICTO entre sí. Es solo una cuestión de cómo organizar su código para reutilizar un HttpClient Y aún así eliminarlo correctamente.

Un par respuesta más larga citado de mi otra respuesta :

No es una coincidencia ver a la gente in some blog posts blaming how HttpClient ' s IDisposable interface hace que tiendan a usar el patrón using (var client = new HttpClient()) {...} y luego llevar al zócalo agotado problema del manejador.

Creo que eso se reduce a un tácito (mis?)concepción: "se espera que un objeto identificable sea de corta duración".

SIN EMBARGO, aunque ciertamente parece una cosa de corta duración cuando escribimos código en este estilo:

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

La documentación oficial sobre identificable nunca menciona IDisposable los objetos tienen que ser de corta duración. Por definición, IDesposable es simplemente un mecanismo que le permite liberar recursos no administrados. Nada más. En ese sentido, se ESPERA que eventualmente desencadene la eliminación, pero no requiere que lo hagas de una manera efímera.

Por lo tanto, es su trabajo elegir correctamente cuándo activar la eliminación, base en 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 revisitamos esa publicación del blog , podemos notar claramente que el" arreglo " inicializa HttpClient una vez pero nunca lo deseches, es por eso que podemos ver en su salida netstat que, la conexión permanece en el estado ESTABLECIDO, lo que significa que 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 finalice todo el programa, y el poster del blog todavía ve una ganancia de rendimiento después de la corrección; pero aún así, es conceptualmente incorrecto culpa IDisposable y NO disponer de él.

 0
Author: RayLuo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-31 20:19:22

No hay necesidad de llamar a Dispose Porque HttpClient hereda la clase HttpMessageInvoker y HttpMessageInvoker implementa la interfaz IDisposal y HttpClientHandler heredan la clase HttpMessageHandler y HttpMessageHandler implementan la interfaz IDisposal

 0
Author: Sunil Dhappadhule,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-10-11 13:15:41

Creo que uno debería usar el patrón singleton para evitar tener que crear instancias del HttpClient y cerrarlo todo el tiempo. Si está utilizando. Net 4.0, puede usar un código de ejemplo como se muestra a continuación. para obtener más información sobre el patrón singleton, verifique aquí.

class HttpClientSingletonWrapper : HttpClient
{
    private static readonly Lazy<HttpClientSingletonWrapper> Lazy= new Lazy<HttpClientSingletonWrapper>(()=>new HttpClientSingletonWrapper()); 

    public static HttpClientSingletonWrapper Instance {get { return Lazy.Value; }}

    private HttpClientSingletonWrapper()
    {
    }
}

Utilice el código como se muestra a continuación.

var client = HttpClientSingletonWrapper.Instance;
 -3
Author: yayadavid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-11 15:44:48