Do HttpClient and HttpClientHandler have to be disposed between requests?

前端 未结 12 1904
鱼传尺愫
鱼传尺愫 2020-11-22 02:06

System.Net.Http.HttpClient and System.Net.Http.HttpClientHandler in .NET Framework 4.5 implement IDisposable (via System.Net.Http.HttpMessageInvoker).

The usin

相关标签:
12条回答
  • 2020-11-22 02:24

    In my understanding, calling Dispose() is necessary only when it's locking resources you need later (like a particular connection). It's always recommended to free resources you're no longer using, even if you don't need them again, simply because you shouldn't generally be holding onto resources you're not using (pun intended).

    The Microsoft example is not incorrect, necessarily. All resources used will be released when the application exits. And in the case of that example, that happens almost immediately after the HttpClient is done being used. In like cases, explicitly calling Dispose() is somewhat superfluous.

    But, in general, when a class implements IDisposable, the understanding is that you should Dispose() of its instances as soon as you're fully ready and able. I'd posit this is particularly true in cases like HttpClient wherein it's not explicitly documented as to whether resources or connections are being held onto/open. In the case wherein the connection will be reused again [soon], you'll want to forgo Dipose()ing of it -- you're not "fully ready" in that case.

    See also: IDisposable.Dispose Method and When to call Dispose

    0 讨论(0)
  • 2020-11-22 02:24

    In typical usage (responses<2GB) it is not necessary to Dispose the HttpResponseMessages.

    The return types of the HttpClient methods should be Disposed if their Stream Content is not fully Read. Otherwise there is no way for the CLR to know those Streams can be closed until they are garbage collected.

    • If you are reading the data into a byte[] (e.g. GetByteArrayAsync) or string, all data is read, so there is no need to dispose.
    • The other overloads will default to reading the Stream up to 2GB (HttpCompletionOption is ResponseContentRead, HttpClient.MaxResponseContentBufferSize default is 2GB)

    If you set the HttpCompletionOption to ResponseHeadersRead or the response is larger than 2GB, you should clean up. This can be done by calling Dispose on the HttpResponseMessage or by calling Dispose/Close on the Stream obtained from the HttpResonseMessage Content or by reading the content completely.

    Whether you call Dispose on the HttpClient depends on whether you want to cancel pending requests or not.

    0 讨论(0)
  • 2020-11-22 02:26

    In my case, I was creating an HttpClient inside a method that actually did the service call. Something like:

    public void DoServiceCall() {
      var client = new HttpClient();
      await client.PostAsync();
    }
    

    In an Azure worker role, after repeatedly calling this method (without disposing the HttpClient), it would eventually fail with SocketException (connection attempt failed).

    I made the HttpClient an instance variable (disposing it at the class level) and the issue went away. So I would say, yes, dispose the HttpClient, assuming its safe (you don't have outstanding async calls) to do so.

    0 讨论(0)
  • 2020-11-22 02:29

    I think one should use singleton pattern to avoid having to create instances of the HttpClient and closing it all the time. If you are using .Net 4.0 you could use a sample code as below. for more information on singleton pattern check here.

    class HttpClientSingletonWrapper : HttpClient
    {
        private static readonly Lazy<HttpClientSingletonWrapper> Lazy= new Lazy<HttpClientSingletonWrapper>(()=>new HttpClientSingletonWrapper()); 
    
        public static HttpClientSingletonWrapper Instance {get { return Lazy.Value; }}
    
        private HttpClientSingletonWrapper()
        {
        }
    }
    

    Use the code as below.

    var client = HttpClientSingletonWrapper.Instance;
    
    0 讨论(0)
  • 2020-11-22 02:31

    Since it doesn't appear that anyone has mentioned it here yet, the new best way to manage HttpClient and HttpClientHandler in .NET Core 2.1 is using HttpClientFactory.

    It solves most of the aforementioned issues and gotchas in a clean and easy-to-use way. From Steve Gordon's great blog post:

    Add the following packages to your .Net Core (2.1.1 or later) project:

    Microsoft.AspNetCore.All
    Microsoft.Extensions.Http
    

    Add this to Startup.cs:

    services.AddHttpClient();
    

    Inject and use:

    [Route("api/[controller]")]
    public class ValuesController : Controller
    {
        private readonly IHttpClientFactory _httpClientFactory;
    
        public ValuesController(IHttpClientFactory httpClientFactory)
        {
            _httpClientFactory = httpClientFactory;
        }
    
        [HttpGet]
        public async Task<ActionResult> Get()
        {
            var client = _httpClientFactory.CreateClient();
            var result = await client.GetStringAsync("http://www.google.com");
            return Ok(result);
        }
    }
    

    Explore the series of posts in Steve's blog for lots more features.

    0 讨论(0)
  • 2020-11-22 02:32

    If you want to dispose of HttpClient, you can if you set it up as a resource pool. And at the end of your application, you dispose your resource pool.

    Code:

    // Notice that IDisposable is not implemented here!
    public interface HttpClientHandle
    {
        HttpRequestHeaders DefaultRequestHeaders { get; }
        Uri BaseAddress { get; set; }
        // ...
        // All the other methods from peeking at HttpClient
    }
    
    public class HttpClientHander : HttpClient, HttpClientHandle, IDisposable
    {
        public static ConditionalWeakTable<Uri, HttpClientHander> _httpClientsPool;
        public static HashSet<Uri> _uris;
    
        static HttpClientHander()
        {
            _httpClientsPool = new ConditionalWeakTable<Uri, HttpClientHander>();
            _uris = new HashSet<Uri>();
            SetupGlobalPoolFinalizer();
        }
    
        private DateTime _delayFinalization = DateTime.MinValue;
        private bool _isDisposed = false;
    
        public static HttpClientHandle GetHttpClientHandle(Uri baseUrl)
        {
            HttpClientHander httpClient = _httpClientsPool.GetOrCreateValue(baseUrl);
            _uris.Add(baseUrl);
            httpClient._delayFinalization = DateTime.MinValue;
            httpClient.BaseAddress = baseUrl;
    
            return httpClient;
        }
    
        void IDisposable.Dispose()
        {
            _isDisposed = true;
            GC.SuppressFinalize(this);
    
            base.Dispose();
        }
    
        ~HttpClientHander()
        {
            if (_delayFinalization == DateTime.MinValue)
                _delayFinalization = DateTime.UtcNow;
            if (DateTime.UtcNow.Subtract(_delayFinalization) < base.Timeout)
                GC.ReRegisterForFinalize(this);
        }
    
        private static void SetupGlobalPoolFinalizer()
        {
            AppDomain.CurrentDomain.ProcessExit +=
                (sender, eventArgs) => { FinalizeGlobalPool(); };
        }
    
        private static void FinalizeGlobalPool()
        {
            foreach (var key in _uris)
            {
                HttpClientHander value = null;
                if (_httpClientsPool.TryGetValue(key, out value))
                    try { value.Dispose(); } catch { }
            }
    
            _uris.Clear();
            _httpClientsPool = null;
        }
    }
    

    var handler = HttpClientHander.GetHttpClientHandle(new Uri("base url")).

    • HttpClient, as an interface, can't call Dispose().
    • Dispose() will be called in a delayed fashion by the Garbage Collector. Or when the program cleans up the object through its destructor.
    • Uses Weak References + delayed cleanup logic so it remains in use so long as it is being reused frequently.
    • It only allocates a new HttpClient for each base URL passed to it. Reasons explained by Ohad Schneider answer below. Bad behavior when changing base url.
    • HttpClientHandle allows for Mocking in tests
    0 讨论(0)
提交回复
热议问题