我正在使用客户端库来访问第三方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属性,因此调试器从何处获取它?
我猜测对asp.net Web应用程序的http请求具有其自己的令牌和源,因此HttpClient会以某种方式使用它。但是,客户端正在等待所有异步调用以同步获取结果,因此,由于我们尚未从客户端库的调用返回,因此我不了解如何处理基础CTS。
任何人都可以了解正在发生的事情并且有解决方案吗?
答案 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