1

我正在使用客户端库来访问第 3 方 API。该库由 NSwagStudio 从 Swagger 文档生成。

我正在开发的应用程序在其所有调用中都是完全同步的,并且将其更新为异步超出了我正在开发的范围。

当我从单元测试中测试客户端库时,它工作正常。当我尝试从 ASP.Net 应用程序中调用它时,我收到以下错误:

CancellationTokenSource 已被释放。

我已经将客户端库提炼为演示问题的要点,我选择了一个选项来提供同步方法和异步:

public class ClientApi
{
    private readonly HttpClient _httpClient;

    public ClientApi(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public string BaseUrl { get; set; }

    public object Get()
    {
        return Task.Run(async () => await GetAsync(CancellationToken.None)).GetAwaiter().GetResult();
    }

    /// <returns>OK</returns>
    /// <param name="cancellationToken">
    ///     A cancellation token that can be used by other objects or threads to receive notice of
    ///     cancellation.
    /// </param>
    public async Task<string> GetAsync(CancellationToken cancellationToken)
    {
        var client_ = _httpClient;
        try
        {
            using (var request_ = new HttpRequestMessage())
            {
                request_.Method = new HttpMethod("GET");
                request_.RequestUri = new System.Uri(BaseUrl, System.UriKind.RelativeOrAbsolute);

                var response_ = await client_.SendAsync(
                    request_, 
                    HttpCompletionOption.ResponseHeadersRead, 
                    cancellationToken
                ).ConfigureAwait(false);

                try
                {
                    // Exception occurs on following line
                    var responseData_ = response_.Content == null 
                        ? null 
                        : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
                    return responseData_;
                }
                finally
                {
                    response_?.Dispose();
                }
            }
        }
        finally { }
    }
}

这是调用它的代码:

    protected void OnClick(object sender, EventArgs e)
    {
        var httpClient = new HttpClient();

        var client = new ClientApi(httpClient)
        {
            BaseUrl = "https://www.google.com"
        };

        var html = client.Get();
    }

调用它的代码只是一个带有按钮的 asp.net 页面,并且按钮事件运行与通过的单元测试相同的代码。

当我在调试器中比较运行时:从单元测试中,response_.Content 对象没有取消令牌,但是当从 asp.net 运行时它有。事实上,它们几乎看起来是不同的对象,尽管 GetType() 将它们都报告为 System.Net.Http.StreamContent。从反编译类,这没有 _cancellationtoken 属性,那么调试器从哪里得到它?

来自 Asp.Net

从单元测试

我猜对我的asp.net web 应用程序的http 请求有它自己的令牌和源,它以某种方式被HttpClient 使用。但是,客户端正在等待所有异步调用以同步获取结果,所以我不明白如何处理底层 CTS,因为我们还没有从客户端库的调用中返回。

任何人都可以理解发生了什么并且有解决方案吗?

4

1 回答 1

0

首先,您应该真正重新考虑重写您的客户端应用程序,以便您可以一直实现异步

“一路异步”意味着你不应该在不仔细考虑后果的情况下混合同步和异步代码。特别是,通过调用 Task.Wait 或 Task.Result 来阻止异步代码通常是个坏主意。

取自这个伟大的指南。

基本上,通过运行异步代码同步,你总是会做错事。

但是,如果您确实需要一种解决方案,请首先将一次性对象包装在 using 语句中,而不是手动处理它们。这是您的 ClientApi 类的简化解决方案,它可以满足您的需要(但它可能会死锁)。该代码与此答案中的代码基本相同。

public class ClientApi
{
    public object Get(string url)
    {
        using (var client = new HttpClient())
        {
            var response = client.GetAsync(url).Result;
            if (response.IsSuccessStatusCode)
            {
                var responseContent = response.Content;
                return responseContent.ReadAsStringAsync().Result;
            }
        }

    }
}

在此处阅读有关死锁的更多信息

于 2018-09-11T18:03:55.117 回答