为什么我不能使用HttpClient进行来自ASP.Net的同步调用?

时间:2018-09-11 15:18:14

标签: c# asp.net httpclient cancellation-token sendasync

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

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

当我从单元测试中测试客户端库时,它可以正常工作。当我尝试从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属性,因此调试器从何处获取它?

From Asp.Net

From Unit Test

我猜测对asp.net Web应用程序的http请求具有其自己的令牌和源,因此HttpClient会以某种方式使用它。但是,客户端正在等待所有异步调用以同步获取结果,因此,由于我们尚未从客户端库的调用返回,因此我不了解如何处理基础CTS。

任何人都可以了解正在发生的事情并且有解决方案吗?

1 个答案:

答案 0 :(得分:0)

首先,您应该重新考虑重写客户端应用程序,以便可以完全实现异步

  

“一路异步”表示您不应将同步和   异步代码而没有仔细考虑后果。在   特别是,通过调用阻止异步代码通常是一个坏主意   Task.Wait或Task.Result。

摘自this优秀指南。

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

但是,如果您确实需要一种解决方案,请先使用语句包装可抛弃的对象,而不要手动处置它们。 这是ClientApi类的简化解决方案,可以满足您的需要(但可能会死锁)。代码基本上与此answer中的代码相同。

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

    }
}

详细了解死锁here