2015-01-01 13 views
17

ho ovunque da 10-150 oggetti lunghi di vita della classe che chiamano i metodi che effettuano chiamate API semplice HTTPS che utilizzano HttpClient. Esempio di richiamo PUT:HttpClientHandler/HttpClient Perdita di memoria

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; 
     } 
    } 
} 

Dopo 2-3 ore di esecuzione di questi metodi, che includono il corretto smaltimento tramite using istruzioni, il programma ha creeped a 1 GB-1,5 GB di memoria e infine si blocca con vari su memoria errori. Molte volte le connessioni avvengono tramite proxy inaffidabili, pertanto le connessioni potrebbero non essere completate come previsto (timeout e altri errori sono comuni).

.NET Memory Profiler ha indicato che HttpClientHandler è il problema principale qui, affermando che ha sia "Istanze eliminate con radici delegate dirette" (punto esclamativo rosso) sia "Istanze che sono state eliminate ma non sono ancora GC" (giallo punto esclamativo). I delegati che il profiler indica sono stati rootati sono AsyncCallback s, derivanti da HttpWebRequest.

Può anche riferirsi a RemoteCertValidationCallback, qualcosa a che fare con la convalida del certificato HTTPS, poiché lo TlsStream è un oggetto più in basso nella radice che è "Disposto ma non GCed".

Con tutto questo in mente, come posso utilizzare più correttamente HttpClient ed evitare questi problemi di memoria? Dovrei forzare un GC.Collect() ogni ora o così? So che è considerato una cattiva pratica, ma non so in quale altro modo recuperare questo ricordo che non è del tutto corretto, e un modello di utilizzo migliore per questi oggetti di breve durata non mi sembra così come sembra essere un difetto negli oggetti .NET stessi.


UPDATE Forzare GC.Collect() non ha avuto effetto.

I byte gestiti totali per il processo rimangono coerenti intorno ai 20-30 MB al massimo mentre la memoria complessiva del processo (in Task Manager) continua a salire, indicando una perdita di memoria non gestita. Quindi questo schema di utilizzo sta creando una perdita di memoria non gestita.

Ho provato a creare istanze di livello di classe di HttpClient e HttpClientHandler come suggerito, ma questo non ha avuto alcun effetto apprezzabile. Anche quando li ho impostati a livello di classe, vengono comunque ricreati e raramente riutilizzati a causa del fatto che spesso le impostazioni del proxy richiedono modifiche. HttpClientHandler non consente la modifica delle impostazioni del proxy o delle proprietà una volta che è stata avviata una richiesta, quindi creo costantemente il gestore, proprio come era stato originariamente fatto con le istruzioni indipendenti using.

HttpClienthandler è ancora in fase di smaltimento con "dirette deleghe dirette" su AsyncCallback -> HttpWebRequest. Sto iniziando a chiedermi se forse HttpClient non fosse stato progettato per richieste veloci e oggetti a vita breve. Nessuna fine in vista ... sperando che qualcuno abbia un suggerimento per rendere praticabile l'uso di HttpClientHandler.


colpi Memory Profiler: Initial stack indicating that HttpClientHandler is the root issue, having 304 live instances that should have been GC'd

enter image description here

enter image description here

+0

Perché si smaltiscono 'HttpClientHandler' e' HttpClient' in ogni chiamata? 'HttpClient' dovrebbe essere un oggetto longevo in tutta l'applicazione (e di conseguenza, dovrebbe essere' HttpClientHandler'. In questo modo, è sufficiente generare un'istanza. –

+0

Ho tagliato parti del codice non importanti, ma le intestazioni variano a seconda della richiesta e i proxy cambiano in base al loro successo/alla mancanza di successo con la connessione: si sarebbe verificata molta manutenzione sull'oggetto HttpClientHandler, quindi ho pensato che sarebbe più semplice ricrearlo ogni volta, tuttavia non soddisfa ancora domanda sul perché questi oggetti non possono essere ricreati ripetutamente senza perdite – user1111380

+0

Se provi GC.collect(), vedi questi oggetti TlsStream che vengono raccolti? – zaitsman

risposta

13

Utilizzando il modulo di Repro Alexandr Nikitin, sono stato in grado di scoprire che questo sembra accadere solo quando si ha HttpClient essere un oggetto di breve durata. Se si effettua il gestore ed il cliente lungo vissuto questo non sembra accadere:

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(); 
     } 
    } 
} 
+2

Grazie per essere arrivato a fondo. Sfortunatamente la classe HttpClient non soddisfa i miei requisiti quindi - a causa della natura dinamica e instabile dei proxy pubblici, gli oggetti devono essere spesso ricreati. Sembra che HttpClient non sia una soluzione fattibile per connessioni di breve durata: la modifica delle impostazioni proxy richiede la ricostruzione di HttpClientHandler e quindi di HttpClient. In entrambi i casi, gli oggetti dovrebbero essere in grado di vivere il più a lungo possibile o breve senza perdite; questo sicuramente sembra essere un difetto in HttpClient. – user1111380

0

Come detto Matt Clark, il default HttpClient perdite quando lo si utilizza come un oggetto di breve durata e creare nuovi HttpClients per richiesta.

Per aggirare il problema, sono stato in grado di continuare a utilizzare HttpClient come un oggetto di breve durata utilizzando il seguente pacchetto Nuget al posto del built-in System.Net.Http assemblaggio: https://www.nuget.org/packages/HttpClient

Non sono sicuro che l'origine di questo pacchetto è, tuttavia, non appena ho fatto riferimento a esso, la perdita di memoria è scomparsa. Assicurarsi di rimuovere il riferimento alla libreria .NET integrata System.Net.Http e utilizzare invece il pacchetto Nuget.

+0

sfortunatamente sembra che il proprietario non abbia elencato questo pacchetto "Il proprietario non ha elencato questo pacchetto Questo potrebbe significare che il pacchetto è deprecato o non dovrebbe più essere usato." –

+0

Puoi ancora usarlo anche se non è in elenco. Funziona ancora. –

0

Ecco come cambio il proxy HttpClientHandler senza ricreare l'oggetto.

public static void ChangeProxy(this HttpClientHandler handler, WebProxy newProxy) 
{ 
    if (handler.Proxy is WebProxy currentHandlerProxy) 
    { 
     currentHandlerProxy.Address = newProxy.Address; 
     currentHandlerProxy.Credentials = newProxy.Credentials; 
    } 
    else 
    { 
     handler.Proxy = newProxy; 
    } 
}