tutorial - using httpclient postasync c#




¿Es necesario eliminar HttpClient y HttpClientHandler? (8)

System.Net.Http.HttpClient y System.Net.Http.HttpClientHandler en .NET Framework 4.5 implementan IDisposable (vía System.Net.Http.HttpMessageInvoker )

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

Como regla general, cuando utiliza un objeto IDisposable, debe declararlo y crear una instancia 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 llaman a Dispose() explícita o implícita. Por ejemplo:

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 disponer en la instancia de HttpClient. He usado todas las instancias de HttpClient con el uso de la declaración 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 al "usar" y asincronizar, ya que no se mezclan realmente en .Net 4. En .Net 4.5 puede usar "esperar" dentro de una declaración de "usar".

Por cierto, puede reutilizar el mismo HttpClient tantas veces como desee, por lo general, no las creará ni las desechará todo el tiempo.

El segundo párrafo es superfluo para esta pregunta, que no le preocupa cuántas veces puede usar una instancia de HttpClient, pero si es necesario desecharla después de que ya no la necesite.

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

Así que mis preguntas son:

  1. ¿Es necesario, dada la implementación actual (.NET Framework 4.5), para llamar a Dispose () en las instancias HttpClient y HttpClientHandler? Aclaración: por "necesario" quiero decir si hay alguna consecuencia negativa 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 implementan IDisposable?

  3. Si es necesario (o recomendado), ¿ este código mencionado anteriormente lo implementa de manera 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, ¿son los ejemplos de Microsoft engañosos o inseguros?


No es necesario llamar a Dispose porque HttpClient hereda la clase HttpMessageInvoker y la clase HttpMessageInvoker implementa la Interfaz IDispos y HttpClientHandler hereda la clase HttpMessageHandler y la interfaz HttpMessageHandler implementan la interfaz IDisposal


Creo que uno debería usar el patrón de 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 muestra como se muestra a continuación. para obtener más información sobre el patrón de singleton verifique here

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

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

    private HttpClientSingletonWrapper()
    {
    }
}

Usa el código de la siguiente manera.

var client = HttpClientSingletonWrapper.Instance;

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

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

Consulte la publicación del blog de Darrel Miller y una publicación de SO relacionada: Resultados de rastreo de HttpClient en la pérdida de memoria para 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 sucede debajo del capó, en particular la sección "Ciclo de vida" citada aquí:

Aunque HttpClient implementa indirectamente la interfaz IDisposable, el uso estándar de HttpClient es no desecharla después de cada solicitud. El objeto HttpClient está destinado a vivir mientras su aplicación necesite realizar solicitudes HTTP. La existencia de un objeto 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 fue necesario con HttpWebRequest.

O incluso abrir DotPeek.


El uso de la inyección de dependencias en su constructor facilita la administración de la vida útil de su HttpClient , haciendo que la gestión de la vida útil quede fuera del código que lo necesita y se pueda cambiar fácilmente en una fecha posterior.

Mi preferencia actual es crear una clase de cliente http separada que herede de HttpClient una vez por dominio de punto final de destino y luego convertirla en un singleton utilizando la inyección de dependencia. public class ExampleHttpClient : HttpClient { ... }

Luego tomo una dependencia de constructor del 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.

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


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

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

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

Hice de HttpClient una variable de instancia (eliminándola a nivel de clase) y el problema desapareció. Así que diría, sí, desechar el HttpClient, asumiendo que es seguro (no tiene llamadas asíncronas pendientes) para hacerlo.


En mi entendimiento, llamar a Dispose() es necesario solo cuando está bloqueando los recursos que necesita más tarde (como una conexión en particular). Siempre se recomienda liberar los recursos que ya no está utilizando, incluso si no los necesita de nuevo, simplemente porque generalmente no debería conservar los recursos que no está usando (destinado a un juego de palabras).

El ejemplo de Microsoft no es incorrecto, necesariamente. Todos los recursos utilizados se liberarán cuando la aplicación salga. Y en el caso de ese ejemplo, eso sucede casi inmediatamente después de que se utiliza el HttpClient . 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() sus instancias tan pronto como esté listo y capaz. Yo diría que esto es particularmente cierto en casos como HttpClient donde no se documenta explícitamente si los recursos o las conexiones se mantienen o se abren. En el caso en el que la conexión se volverá a utilizar [pronto], querrá renunciar a Dipose() , no está "completamente preparado" en ese caso.

Consulte también: Método IDisposable.Disponer y cuándo llamar a Dispose


Si desea deshacerse de HttpClient, puede hacerlo si lo configura como un grupo de recursos. Y al final de su aplicación, dispone 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;
    }
}

controlador de var = HttpClientHander.GetHttpClientHandle (nuevo Uri ("url base")).

  • HttpClient, como interfaz, no puede llamar a Dispose ().
  • El recolector de basura llamará a Dispose () con retraso. O cuando el programa limpia el objeto a través de su destructor.
  • Usa referencias débiles + demora en la lógica de limpieza, por lo que permanece en uso mientras se reutiliza con frecuencia.
  • Solo asigna un nuevo HttpClient para cada URL base que se le pasa. Las razones explicadas por Ohad Schneider responden a continuación. Mal comportamiento al cambiar la url base.
  • HttpClientHandle permite simulación en pruebas

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

Respuesta larga : AMBAS de las siguientes afirmaciones son verdaderas y alcanzables 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 un objeto IDisposable se IDisposable .

Y NO SON CONFLICTOS NECESARIOS entre sí. Es solo una cuestión de cómo organizar su código para reutilizar un HttpClient Y aún así disponerlo adecuadamente.

Una respuesta aún más larga citada de mi otra respuesta :

No es una coincidencia ver a la gente en algunas publicaciones del blog culpando a cómo la interfaz IDisposable HttpClient hace que utilicen el patrón de using (var client = new HttpClient()) {...} y luego conducir al problema del controlador de socket agotado.

Creo que se trata de una concepción tácita (¿errónea?): "Se espera que un objeto IDisponible sea de corta duración" .

SIN EMBARGO, aunque 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 los objetos IDisposable tienen 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 eventualmente se desencadene la eliminación, pero no es necesario que lo haga de manera breve.

Por lo tanto, es su trabajo elegir correctamente cuándo desencadenar la eliminación, basándose en el requisito del ciclo de vida de su objeto real. No hay nada que le impida utilizar 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 esta nueva comprensión, ahora volvemos a visitar la publicación del blog , podemos notar claramente que el "arreglo" inicializa HttpClient una vez, pero nunca lo desechamos, 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 después de que finalice todo el programa, y ​​el póster del blog sigue viendo un aumento de rendimiento después de la corrección; pero aún así, es conceptualmente incorrecto culpar a IDisposable y elegir NO desecharlo.





dotnet-httpclient