27

I've simplified the code a bit but basically this keep giving me a "Cannot access a disposed object." error and I cant work out why?

I have multiple tasks running simultaneously that perform a GET then parse some HTML then perform a POST depending on the results of the GET.

The method this code resides in returns an event object with results so I don't think I can use await because the method would need to return void?

foreach (Account accountToCheck in eventToCheck.accountsToRunOn)
{
    Task.Run(() =>
    {
        HttpClientHandler handler = new HttpClientHandler();
        CookieContainer cookies = new CookieContainer();
        handler.CookieContainer = cookies;
        using (var client = new HttpClient(handler))
        {
            ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; };
            client.Timeout = new TimeSpan(0, 0, 3);
            client.DefaultRequestHeaders.Add("Keep-Alive", "false");
            HttpResponseMessage response = client.GetAsync("https://test.com", HttpCompletionOption.ResponseContentRead).Result;
            string html = response.Content.ReadAsStringAsync().Result;

            var content = new FormUrlEncodedContent(new[]
            {
                new KeyValuePair<string, string>("test[username_or_email]",  accountToLogIn.accountHandle),
                new KeyValuePair<string, string>("test[password]",           accountToLogIn.accountPassword)
            });

            var loginPostResult = client.PostAsync("https://test.com/login", content).Result;

            loginHTMl = convertToUTF8(loginPostResult.Content.ReadAsStringAsync().Result);
        }
    });
}

Exception.

Unable to read data from the transport connection: Cannot access a disposed object.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Mr J
  • 2,655
  • 4
  • 37
  • 58
  • Which line is giveing you that exception? Post the entire stack trace in your question. – CathalMF Apr 18 '16 at 15:52
  • Can you post the actual stack trace of the exception that you are seeing., – CathalMF Apr 18 '16 at 15:57
  • I've edited the question, hopefully that helps a little, let me know what else I can add :-) – Mr J Apr 18 '16 at 15:58
  • I think, it is just, because You ran out of scope of Your using, after ReadStringAsync returns. So the loginPostresult is disposed automatically, because of the using directive. – icbytes Apr 18 '16 at 15:59
  • In your actual running code is loginPostResult and the convertToUTF8 inside or outside of the Using block? – CathalMF Apr 18 '16 at 16:03
  • CathalMF: just count the braces :-D – icbytes Apr 18 '16 at 16:04
  • @icbytes He said he simplified the code. The code posted is not what he is running. – CathalMF Apr 18 '16 at 16:05
  • @jamie Remove the using block and see if the problem persists. – CathalMF Apr 18 '16 at 16:12
  • Note that this might be one of the few exceptions where the using / dispose pattern isn't best practice - see https://stackoverflow.com/questions/15705092/do-httpclient-and-httpclienthandler-have-to-be-disposed – James Westgate Jun 27 '18 at 18:26

5 Answers5

55

Ok after a bit of research i found the issue. The HttpClientHandler will get disposed after the first request. You need to instruct your system not to dispose the handler.

Change your using to add false to the constructor.

using (var client = new HttpClient(handler, false))
{

}
CathalMF
  • 9,705
  • 6
  • 70
  • 106
9

It's a good practice to reuse HttpClientHandler instance to prevent disposing.

Also personally I prefer more clear syntax with minimizing Task.Result calls.

// single setup of client handler
HttpClientHandler handler = new HttpClientHandler();

var tasks = eventToCheck.accountsToRunOn.Select(async () => {
    // ...
    using (var client = new HttpClient(handler, false)) // pass false to prevent Disposing
    {
        // ...
        var html = await response.Content.ReadAsStringAsync();
        // ...
        return loginHtml;
    }
});

// get array of results
string[] loginsHtml = await Task.WhenAll(tasks);
jonny.novikov
  • 126
  • 1
  • 5
1

If you are using .NET 6 and Dependency Injection, make sure AddScoped line is before the AddHttpClient line. Example:

builder.Services.AddScoped<IService, Service>();
builder.Services.AddHttpClient<IService, Service>();

Doing the other way around causes the cannot-access-a-disposed-object issue.

user88243
  • 41
  • 2
0

If we are using containers for injecting the Dependency of HttpClient, then "using" is not required. The container will manage the life time by itself

Ajith V S
  • 56
  • 3
0

Another case I encountered is when operating an Azure Function. When the timeout expires, you get such a message for whatever service you are using.

GeorgiG
  • 1,018
  • 1
  • 13
  • 29