problem net close c# memory memory-leaks garbage-collection httpclient

net - c# httpclient static



HttpClientHandler/HttpClient Memory Leak (2)

Tengo entre 10 y 150 objetos de clase viva larga que llaman a métodos que realizan llamadas API HTTPS simples usando HttpClient. Ejemplo de una llamada PUT:

using (HttpClientHandler handler = new HttpClientHandler()) { handler.UseCookies = true; handler.CookieContainer = _Cookies; using (HttpClient client = new HttpClient(handler, true)) { client.Timeout = new TimeSpan(0, 0, (int)(SettingsData.Values.ProxyTimeout * 1.5)); client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", Statics.UserAgent); try { using (StringContent sData = new StringContent(data, Encoding.UTF8, contentType)) using (HttpResponseMessage response = await client.PutAsync(url, sData)) { using (var content = response.Content) { ret = await content.ReadAsStringAsync(); } } } catch (ThreadAbortException) { throw; } catch (Exception ex) { LastErrorText = ex.Message; } } }

Después de 2-3 horas de ejecutar estos métodos, que incluyen la eliminación adecuada a través de declaraciones de using , el programa se ha incrementado a 1GB-1.5GB de memoria y finalmente se bloquea con varios errores de memoria. Muchas veces las conexiones son a través de proxies no confiables, por lo que las conexiones pueden no completarse como se espera (los tiempos de espera y otros errores son comunes).

.NET Memory Profiler ha indicado que HttpClientHandler es el principal problema aquí, indicando que tiene tanto ''instancias eliminadas con raíces delegadas directas'' (signo de exclamación rojo) como ''instancias que han sido eliminadas pero que todavía no tienen GC'' (signo de exclamación amarillo). Los delegados que indica el perfilador han rooteado son AsyncCallback s, que proviene de HttpWebRequest.

También puede estar relacionado con RemoteCertValidationCallback , algo que tiene que ver con la validación de certificados HTTPS, ya que TlsStream es un objeto más abajo en la raíz que está ''Disposed pero no GCed''.

Con todo esto en mente, ¿cómo puedo usar HttpClient de manera más correcta y evitar estos problemas de memoria? ¿Debo forzar un GC.Collect() cada hora más o menos? Sé que se considera una mala práctica, pero no sé de qué otra forma puedo reclamar esta memoria que no se está desechando correctamente, y no me resulta evidente un mejor patrón de uso para estos objetos efímeros, ya que parece ser un defecto en los objetos .NET.

ACTUALIZAR Forzar GC.Collect() no tuvo ningún efecto.

Los bytes administrados totales para el proceso permanecen constantes en torno a 20-30 MB como máximo mientras que la memoria general del proceso (en el Administrador de tareas) continúa ascendiendo, lo que indica una pérdida de memoria no administrada. Por lo tanto, este patrón de uso está creando una fuga de memoria no administrada.

He intentado crear instancias de nivel de clase de HttpClient y HttpClientHandler según la sugerencia, pero esto no ha tenido un efecto apreciable. Incluso cuando los establezco en el nivel de clase, todavía se vuelven a crear y rara vez se vuelven a usar debido a que la configuración del proxy a menudo requiere cambios. HttpClientHandler no permite la modificación de la configuración del proxy ni de ninguna propiedad una vez que se ha iniciado una solicitud, por lo que estoy constantemente recreando el controlador, tal como se hizo originalmente con las instrucciones de using independientes.

HttpClienthandler todavía se está eliminando con "raíces delegadas directas" a AsyncCallback -> HttpWebRequest. Estoy comenzando a preguntarme si quizás el HttpClient simplemente no fue diseñado para solicitudes rápidas y objetos de vida corta. No hay un final a la vista ... esperando que alguien tenga una sugerencia para hacer viable el uso de HttpClientHandler.

Tomas de perfil de memoria:


Usando el formulario de reprografía Alexandr Nikitin, pude descubrir que esto parece suceder ÚNICAMENTE cuando tienes HttpClient como un objeto efímero. Si haces que el manejador y el cliente vivan por mucho tiempo, esto no parece suceder:

using System; using System.Net.Http; using System.Threading.Tasks; namespace HttpClientMemoryLeak { using System.Net; using System.Threading; class Program { static HttpClientHandler handler = new HttpClientHandler(); private static HttpClient client = new HttpClient(handler); public static async Task TestMethod() { try { using (var response = await client.PutAsync("http://localhost/any/url", null)) { } } catch { } } static void Main(string[] args) { for (int i = 0; i < 1000000; i++) { Thread.Sleep(10); TestMethod(); } Console.WriteLine("Finished!"); Console.ReadKey(); } } }


Como mencionó Matt Clark, el HttpClient predeterminado HttpClient filtra cuando lo usas como un objeto efímero y creas HttpClients nuevos por solicitud.

Como solución alternativa, pude seguir utilizando HttpClient como un objeto efímero mediante el uso del siguiente paquete Nuget en lugar del ensamblado System.Net.Http incorporado: https://www.nuget.org/packages/HttpClient

No estoy seguro de cuál es el origen de este paquete, sin embargo, tan pronto como lo mencioné desapareció la pérdida de memoria. Asegúrese de eliminar la referencia a la biblioteca incorporada .NET System.Net.Http y, en su lugar, use el paquete Nuget.