2015-01-01 21 views
17

私はHttpClientを使用して簡単なHTTPS API呼び出しを実行するメソッドを呼び出す10-150の長いリビングクラスオブジェクトを持っています。 PUTコールの例:HttpClientHandler/HttpClientメモリリーク

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

using文を経由して適切な処分を含め、これらのメソッドを実行しているの2~3時間後に、プログラムは、1GBのメモリ、1.5ギガバイトにぞっとしており、最終的にメモリ不足様々でクラッシュエラー。多くの場合、接続は信頼性の低いプロキシ経由で行われるため、接続が期待通りに完了しない可能性があります(タイムアウトやその他のエラーが一般的です)。

.NETメモリプロファイラーでは、HttpClientHandlerが主な問題で、「直接の委任ルートを持つインスタンスを廃棄しました」(赤い感嘆符)と「廃棄されたがまだGCedされていないインスタンス」(黄色エクスクラメーション・マーク)。プロファイラが示すルートは、HttpWebRequestに由来するAsyncCallbackです。

TlsStreamは、「廃棄されましたがGCedではない」というルートのオブジェクトであるため、RemoteCertValidationCallbackとHTTPS証明書の検証と関係があります。

これを念頭に置いて、どうすればHttpClientをより正しく使用してこれらのメモリの問題を回避できますか?私は毎時GC.Collect()を強制する必要がありますか?私はそれが悪い習慣と見なされることは知っていますが、私は、このメモリを再処分する方法が他にどれほど適切に処理されているのか分かりませんし、これらの短命オブジェクトのより良い使用パターンは、 .NETオブジェクト自体に欠陥がある。強制GC.Collect()


UPDATE は効果がありませんでした。

プロセス全体の管理されたバイト数は20-30 MB程度ですが、プロセス全体のメモリ(タスクマネージャー内)は上昇し続け、管理されていないメモリリークを示します。したがって、この使用パターンは、アンマネージメモリリークを引き起こしています。

提案ごとにHttpClientとHttpClientHandlerの両方のクラスレベルのインスタンスを作成しようとしましたが、これには大きな影響はありませんでした。これらをクラスレベルに設定しても、プロキシの設定に変更が必要なことが多いため、再作成され、再利用されることはほとんどありません。 HttpClientHandlerでは、リクエストが開始されるとプロキシ設定やプロパティの変更が許可されないため、当初は独立したusingステートメントのようにハンドラを再作成しています。

HttpClienthandlerは、依然としてAsyncCallback - > HttpWebRequestに「直接委任ルート」を使用して処理されています。私はおそらくHttpClientが速い要求と短命のオブジェクトのために設計されていないかどうか疑問に思っています。視界に終わりはありません。誰かがHttpClientHandlerを実行可能にするよう提案することを望むことを望みます。


メモリプロファイラショット: 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

なぜ、各呼び出しで 'HttpClientHandler'と' HttpClient'を処理していますか? –

+0

コードの重要ではない部分を切り捨てましたが、要求に応じてヘッダーが変動します(例:HttpClientHandler)。 HttpClientHandlerオブジェクトのメンテナンスが頻繁に行われるため、毎回それを再作成する方が簡単だと思っていましたが、それでもまだそれを満たすことはできませんでした。なぜこれらのオブジェクトをリークすることなく繰り返し再作成できないのかという質問 – user1111380

+0

GC.collect()を試してみると、これらのTlsStreamオブジェクトが収集されていますか? – zaitsman

答えて

13

REPROフォームアレクサンドル・ニキーチンを使用して、私はこれがあることあなたがのHttpClientを持っているときにのみ起こるようですことを発見することができました短命のオブジェクト。あなたは短命オブジェクトとしてそれを使用する際にマット・クラークは、デフォルトHttpClientリークを述べたように

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

ありがとうございました。残念ながら、HttpClientクラスは私の要件を満たしていません - パブリックプロキシの動的で不安定な性質のため、オブジェクトはしばしば再作成されなければなりません。 HttpClientは、ショートライフ接続の実現可能なソリューションではないようです。プロキシ設定を変更するには、HttpClientHandler、つまりHttpClientを再構築する必要があります。どちらの方法でも、オブジェクトは、漏れなく必要に応じて長くまたは短く生きることができます。これは間違いなくHttpClientの欠陥のようです。 – user1111380

0

、リクエストごとに新しいHttpClientsを作成する:あなたは長寿命ハンドラとクライアントを作る場合はこれが起こるとは思われません。回避策として

は、私の代わりに、次のNugetパッケージを使用することにより、短命オブジェクトとしてのHttpClientを使用し続けることができたビルトイン System.Net.Httpアセンブリ: https://www.nuget.org/packages/HttpClient

わからないものを、このパッケージの起源しかし、私がそれを参照するとすぐにメモリリークが消えてしまったのです。組み込みの.NET System.Net.Httpライブラリへの参照を削除し、代わりにNugetパッケージを使用するようにしてください。

+0

残念ながら、オーナーはこのパッケージを非公開にしているようです。「所有者はこのパッケージを非公開にしています。これは、パッケージが廃止されたか、もう使用しないでください。 –

+0

未登録の場合でも引き続き使用できます。それはまだ動作します。 –

0

これは、オブジェクトを再作成せずにHttpClientHandlerプロキシを変更する方法です。

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