HttpClient と using

C# の REST クライアントといえば HttpClient 。コイツは IDisposable の実装なので、例えばこんな感じで using したくなる。

public class MyClient
{
  public async Task<string> Get(string url)
  {
    using(var client = new HttpClient())
    {
      var response = await client.GetAsync(url);

      switch(response.StatusCode)
      {
        // ステータスで処理分けたり
      }
      
      return await response.Content.ReadAsStringAsync();
    }
  }
}

しかし…… HttpClientDispose されてもコネクションが残ってしまうようだ。そしたら using はバッドプラクティスだ。

http://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/

スレッドセーフらしいので、こんな実装で回避できる。

public class MyClient
{
  private static readonly HttpClient _client = new HttpClient();

  public async Task<string> Get(string url)
  {
    var response = await _client.GetAsync(url);

    // 中略
      
    return await response.Content.ReadAsStringAsync();
  }
}

もう一歩進むと、どうやらコネクションプールの管理は HttpClientHandler がやってるらしい。コイツを static readonly にして、HttpClient のコンストラクタに渡せば、堂々と using できる。第2引数は、HttpClientDispose されたときに HttpClientHandler も一緒に Dispose するかどうか。使いまわすなら false

public class MyClient
{
  private static readonly HttpClientHandler _handler = new HttpClientHandler();

  public async Task<string> Get(string url)
  {
    using(var client = new HttpClient(_handler, false)
    {
      var response = await client.GetAsync(url);

      // 中略
      
      return await response.Content.ReadAsStringAsync();
    }
  }
}

非同期周りもハマりやすい HttpClient 、なかなか曲者だ。

参考: 開発者を苦しめる.NETのHttpClientのバグと紛らわしいドキュメント