HttpContent 在 try 和 catch 之间被处理掉
HttpContent gets disposed between try and catch
我正在对我在 Xamarin.Forms 4.8 中创建的 ASP.NET 核心 Web API 执行简单的 GET 请求。为此,我使用以下代码:
public async Task<Result<bool>> GetSomeResult()
{
var client = service.Client;
HttpResponseMessage response = null;
try
{
response = await client.GetAsync(new UriHelper(endpoint, "someEndpoint")).ConfigureAwait(false);
response.EnsureSuccessStatusCode(); // will throw a exception on a non-success status code
return await response.Content.ReadAsAsync<bool>().ConfigureAwait(false);
}
catch (Exception ex)
{
if (response?.StatusCode == HttpStatusCode.InternalServerError)
{
// !!! The error occurs in the next line !!!
SomeErrorClass id = await response.Content.ReadAsAsync<SomeErrorClass>().ConfigureAwait(false);
return new Result<bool>(SomeErrorClass.ToString());
}
return new Result<bool>(ex);
}
}
service
是一个单例,它被注入到周围 class 的构造函数中(通过 DryIoc)。这个 service
是一个 HttpClient
实例的包装器,除了提供配置 HttpClient
的工具(以及在其配置已更改时处理和替换实例)外,无所事事。因此,在配置之后,只要使用 service.Client
,就会返回相同的 HttpClient
实例。代码是这样的:
public class ServiceConnection
{
private const string Localhost = "https://127.0.0.1";
private readonly PreferenceService preferences;
private readonly IHttpClientHandlerProvider handlerProvider;
public HttpClient Client { get; private set; }
public ServiceConnection(PreferenceService preferences, IHttpClientHandlerProvider handlerProvider)
{
this.preferences = preferences;
this.handlerProvider = handlerProvider;
Client = CreateClient();
}
private HttpClient CreateClient()
{
var handler = new TimeoutHandler()
{
DefaultTimeout = TimeSpan.FromSeconds(10),
InnerHandler = handlerProvider?.GetHandler(opt =>
{
opt.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
opt.UseDefaultCredentials = true;
opt.AllowAutoRedirect = true;
})
};
Uri.TryCreate($"{preferences.Server ?? Localhost}/api/", UriKind.Absolute, out var baseAddress);
var client = new HttpClient(handler, true)
{
BaseAddress = baseAddress,
Timeout = TimeSpan.FromSeconds(20),
};
client.DefaultRequestHeaders.Add(CustomHttpHeaders.DeviceId, preferences.UUID);
return client;
}
public void RefreshConnection()
{
Client?.Dispose();
Client = CreateClient();
}
}
问题出在我尝试从 HttpResponseMessage
读取 HttpContent
的那一行。每当我调用它时,我都会得到一条 System.ObjectDisposedException
说'无法访问已处置的对象。
对象名称:'System.Net.Http.StreamContent'.'.
我已经尝试将 HttpClient
的 disposeHandler
参数设置为 true
和 false
因为我在互联网上看到一些人建议这样做解决了这个问题,但到目前为止我运气不好。
问题不在于处置。这里有两个问题,一个导致另一个:
- 异常用于流量控制。您可以检查响应的状态代码
,而不是抛出失败
- 在 .NET Core 3 之前,EnsureStatusCode 关闭流。
根本原因是使用异常进行流量控制。
这可以通过解决第一个问题来解决,这也会提高性能 很多。抛出异常是昂贵的,比 if
昂贵几个数量级。快 100-1000 倍:
if (response.IsSuccessStatusCode)
{
var value=await response.Content.ReadAsAsync<bool>();
return value; //Should this be `new Result(value) ??
}
else if (response.StatusCode == HttpStatusCode.InternalServerError)
{
var id = await response.Content.ReadAsAsync<SomeErrorClass>();
return new Result<bool>(SomeErrorClass.ToString());
}
else
{
var reason=$"{response.StatusCode}:{response.ReasonPhrase}";
return new Result<bool>(reason);
}
抛出也会混淆响应短语,这会使故障排除变得更加困难
我正在对我在 Xamarin.Forms 4.8 中创建的 ASP.NET 核心 Web API 执行简单的 GET 请求。为此,我使用以下代码:
public async Task<Result<bool>> GetSomeResult()
{
var client = service.Client;
HttpResponseMessage response = null;
try
{
response = await client.GetAsync(new UriHelper(endpoint, "someEndpoint")).ConfigureAwait(false);
response.EnsureSuccessStatusCode(); // will throw a exception on a non-success status code
return await response.Content.ReadAsAsync<bool>().ConfigureAwait(false);
}
catch (Exception ex)
{
if (response?.StatusCode == HttpStatusCode.InternalServerError)
{
// !!! The error occurs in the next line !!!
SomeErrorClass id = await response.Content.ReadAsAsync<SomeErrorClass>().ConfigureAwait(false);
return new Result<bool>(SomeErrorClass.ToString());
}
return new Result<bool>(ex);
}
}
service
是一个单例,它被注入到周围 class 的构造函数中(通过 DryIoc)。这个 service
是一个 HttpClient
实例的包装器,除了提供配置 HttpClient
的工具(以及在其配置已更改时处理和替换实例)外,无所事事。因此,在配置之后,只要使用 service.Client
,就会返回相同的 HttpClient
实例。代码是这样的:
public class ServiceConnection
{
private const string Localhost = "https://127.0.0.1";
private readonly PreferenceService preferences;
private readonly IHttpClientHandlerProvider handlerProvider;
public HttpClient Client { get; private set; }
public ServiceConnection(PreferenceService preferences, IHttpClientHandlerProvider handlerProvider)
{
this.preferences = preferences;
this.handlerProvider = handlerProvider;
Client = CreateClient();
}
private HttpClient CreateClient()
{
var handler = new TimeoutHandler()
{
DefaultTimeout = TimeSpan.FromSeconds(10),
InnerHandler = handlerProvider?.GetHandler(opt =>
{
opt.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
opt.UseDefaultCredentials = true;
opt.AllowAutoRedirect = true;
})
};
Uri.TryCreate($"{preferences.Server ?? Localhost}/api/", UriKind.Absolute, out var baseAddress);
var client = new HttpClient(handler, true)
{
BaseAddress = baseAddress,
Timeout = TimeSpan.FromSeconds(20),
};
client.DefaultRequestHeaders.Add(CustomHttpHeaders.DeviceId, preferences.UUID);
return client;
}
public void RefreshConnection()
{
Client?.Dispose();
Client = CreateClient();
}
}
问题出在我尝试从 HttpResponseMessage
读取 HttpContent
的那一行。每当我调用它时,我都会得到一条 System.ObjectDisposedException
说'无法访问已处置的对象。
对象名称:'System.Net.Http.StreamContent'.'.
我已经尝试将 HttpClient
的 disposeHandler
参数设置为 true
和 false
因为我在互联网上看到一些人建议这样做解决了这个问题,但到目前为止我运气不好。
问题不在于处置。这里有两个问题,一个导致另一个:
- 异常用于流量控制。您可以检查响应的状态代码 ,而不是抛出失败
- 在 .NET Core 3 之前,EnsureStatusCode 关闭流。
根本原因是使用异常进行流量控制。
这可以通过解决第一个问题来解决,这也会提高性能 很多。抛出异常是昂贵的,比 if
昂贵几个数量级。快 100-1000 倍:
if (response.IsSuccessStatusCode)
{
var value=await response.Content.ReadAsAsync<bool>();
return value; //Should this be `new Result(value) ??
}
else if (response.StatusCode == HttpStatusCode.InternalServerError)
{
var id = await response.Content.ReadAsAsync<SomeErrorClass>();
return new Result<bool>(SomeErrorClass.ToString());
}
else
{
var reason=$"{response.StatusCode}:{response.ReasonPhrase}";
return new Result<bool>(reason);
}
抛出也会混淆响应短语,这会使故障排除变得更加困难