C# 'Cannot access a disposed object' 访问 Slack WebAPI 时出错 - 线程或其他?

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

有人可以帮忙吗?我有点困惑

我正在使用 PostAsync 向 Slack 发送消息 API。代码如下。

我正在努力使速率限制代码正确,所以在编写了我认为正确的代码之后,我尝试通过从 for 循环中一遍又一遍地调用代码来触发速率限制(在这种情况下,发布消息)。代码捕获了速率限制并且似乎做了它应该做的事情(等到限制通过然后再试一次),但随后我得到一个异常,通常但并非总是在下次调用它时。

例外是

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

来源是System.Net.Http 堆栈跟踪是:

   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

此时我确定(好吧,99% 确定)问题出在 SendMessageAsync()

我以为是 Thread.Sleep 但当我删除它时,它发生的次数减少了,但仍然会发生。

我试图追踪它失败的时间,几乎每次它似乎都来自 PostAsync(),下一次在速率限制代码为 运行 之后调用它并且函数退出;它可能曾经在 JsonConvert.DeserializeObject() 时失败,而不是在速率限制之后立即失败,但我不能确定,因为我处于调试的早期阶段。

有人可以帮忙吗?这让我发疯...

这是代码(请原谅原始异常处理,它仍在进行中)- 如果需要,我可以提供更多上下文。

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

您需要为每个请求创建一个新的 StringContentPostAsync 将处理内容。

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).

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