简体   繁体   中英

C# 'Cannot access a disposed object' error accessing Slack WebAPI - threading or something else?

Can anyone help? I'm a little confused

I'm using PostAsync to send messages to the Slack API. Code is below.

I'm trying to get the rate limiting code right, so after writing what I think is right I try to trigger the rate limiting by calling the code over and over again from a for loop (in this case, posting messages). The code catches the rate limit and seems to do what it should (wait until the limit as passed and then try again), but then I get an exception, generally but not always the next time it is called.

Exception is

Cannot access a disposed object. Object name: 'System.Net.Http.StringContent'.

Source is System.Net.Http Stack Trace is:

 at System.Net.Http.HttpContent.CheckDisposed() at System.Net.Http.HttpContent.CopyToAsync(Stream stream, TransportContext context) at System.Net.Http.HttpClientHandler.GetRequestStreamCallback(IAsyncResult ar) at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() at MyApp.MyForm.<SendMessageAsync>d__78.MoveNext() in C:\\Users\\James\\source\\repos\\MyApp\\MyApp\\Form1.cs:line 1314

At this point I'm sure (well, 99% sure) the problem is in SendMessageAsync() .

I thought it was the Thread.Sleep but when I remove that it happens less but still happens.

I've tried to track down when it is failing, and nearly every time it seemed to be from the PostAsync() , the next time it is called after the ratelimit code is run and the function exits; it may once have failed at JsonConvert.DeserializeObject() , not immediately after ratelimiting, but I can't be sure as I was in the early stages of debugging.

Can anyone help? It's driving me crazy...

Here's the code (forgive the primitive Exception handling, it's still in progress) - I could provide more context if need be.

    private static readonly HttpClient client = new HttpClient();

    // sends a slack message asynchronously
    public static async Task<Object> SendMessageAsync(string token, string APIMethod, Object msg, string contentType, Type returnType)
    {
        string content;
        switch (contentType)
        {
            case "application/json":
            default:
                // serialize method parameters to JSON
                content = JsonConvert.SerializeObject(msg);
                break;
            case "application/x-www-form-urlencoded":
                var keyValues = msg.ToKeyValue();
                if (keyValues != null)
                {
                    var formUrlEncodedContent = new FormUrlEncodedContent(keyValues);
                    content = await formUrlEncodedContent.ReadAsStringAsync();
                }
                else
                    content = "";
                break;
        }

        StringContent httpContent = new StringContent(content, Encoding.UTF8, contentType);

        // set token in authorization header
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

        try
        {
            Object messageResponse;
            bool doLoop;
            do
            {
                doLoop = false;
                // send message to API
                var response = await client.PostAsync("https://slack.com/api/" + APIMethod, httpContent);

                // fetch response from API
                var responseJson = await response.Content.ReadAsStringAsync();

                // convert JSON response to object
                messageResponse = JsonConvert.DeserializeObject(responseJson, returnType);

                dynamic genResponse = Convert.ChangeType(messageResponse, returnType);  // https://stackoverflow.com/questions/972636/casting-a-variable-using-a-type-variable
                if (genResponse.ok == false && genResponse.error == "ratelimited")
                {
                    if (response.Headers.RetryAfter != null && response.Headers.RetryAfter.Delta != null)
                    {
                        Thread.Sleep((TimeSpan)response.Headers.RetryAfter.Delta);
                        doLoop = true;
                    }
                }
            } while (doLoop);

            return messageResponse;
        }
        catch (Exception x) { throw x; }
    }

You need to create a new StringContent per request. PostAsync will dispose the content.

When a request completes, HttpClient disposes the request content so the user doesn't have to. This also ensures that a HttpContent object is only sent once using HttpClient (similar to HttpRequestMessages that can also be sent only once).

Why do HttpClient.PostAsync and PutAsync dispose the content?

public static async Task<Object> SendMessageAsync(string token, string APIMethod, Object msg, string contentType, Type returnType)
    {
        string content;
        switch (contentType)
        {
            case "application/json":
            default:
                // serialize method parameters to JSON
                content = JsonConvert.SerializeObject(msg);
                break;
            case "application/x-www-form-urlencoded":
                var keyValues = msg.ToKeyValue();
                if (keyValues != null)
                {
                    var formUrlEncodedContent = new FormUrlEncodedContent(keyValues);
                    content = await formUrlEncodedContent.ReadAsStringAsync();
                }
                else
                    content = "";
                break;
        }

        // vvvv --- Move this line from here --- vvvv
        //StringContent httpContent = new StringContent(content, Encoding.UTF8, contentType);

        // set token in authorization header
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

        try
        {
            Object messageResponse;
            bool doLoop;
            do
            {
                doLoop = false;

                // vvvv --- To here --- vvv
                StringContent httpContent = new StringContent(content, Encoding.UTF8, contentType);

                // send message to API
                var response = await client.PostAsync("https://slack.com/api/" + APIMethod, httpContent);

                // fetch response from API
                var responseJson = await response.Content.ReadAsStringAsync();

                // convert JSON response to object
                messageResponse = JsonConvert.DeserializeObject(responseJson, returnType);

                dynamic genResponse = Convert.ChangeType(messageResponse, returnType);  // https://stackoverflow.com/questions/972636/casting-a-variable-using-a-type-variable
                if (genResponse.ok == false && genResponse.error == "ratelimited")
                {
                    if (response.Headers.RetryAfter != null && response.Headers.RetryAfter.Delta != null)
                    {
                        Thread.Sleep((TimeSpan)response.Headers.RetryAfter.Delta);
                        doLoop = true;
                    }
                }
            } while (doLoop);

            return messageResponse;
        }
        catch (Exception x) { throw x; }
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM