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:
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. –
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
Se provi GC.collect(), vedi questi oggetti TlsStream che vengono raccolti? – zaitsman