C# HttpClient 通过 VPN 抛出 System.OutOfMemoryException
C# HttpClient throws System.OutOfMemoryException over VPN
我有一个通过 VPN 使用 Web API 的 WPF 桌面应用程序。相关详情:
- 应用程序类型:WPF
- .NET: 4.6.2
- OS: Windows 10
- VPN:帕洛阿尔托 GlobalProtect
有时它会在应用程序启动时从 HttpClient 抛出未捕获的异常:
Error message: Exception of type 'System.OutOfMemoryException' was thrown.
Stack trace:
at MyApp.Net.Services.RestNetClient.<GetMyRoles>d__5.MoveNext()
at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.Start[TStateMachine](TStateMachine& stateMachine)
at MyApp.Net.Services.RestNetClient.GetMyRoles()
<...>
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at MyApp.Wpf.UI.App.<OnStartup>d__3.MoveNext() in <...>\App.xaml.cs:line 44
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
这里是 RestNetClient
class 实现的相关部分
public class RestNetClient : INetClient {
public RestNetClient(IRemoteConfig config) {
if (Client == null) {
HttpClientHandler authHandler = new HttpClientHandler {
Credentials = CredentialCache.DefaultNetworkCredentials
};
Client = new HttpClient(authHandler) { BaseAddress = config.ServerAddress };
Client.DefaultRequestHeaders.Accept.Clear();
Client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
}
protected static HttpClient Client { get; private set; }
public async Task<IServiceOperationResult<AppRole>> GetMyRoles() {
var result = new ServiceOperationResult<AppRole>();
try {
HttpResponseMessage response = await Client.GetAsync("api/ntds/getmyroles");
if (response.IsSuccessStatusCode) {
String json = await response.Content.ReadAsStringAsync();
result.Value = JsonConvert.DeserializeObject<AppRole>(json);
} else {
await response.SetErrorCode(result);
}
} catch (Exception ex) {
result.HResult = ErrorCode.E_INVALIDARG;
result.ErrorMessage = ex.Message;
}
return result;
}
<...>
异常未在 GetMyRoles
方法的 catch
子句中捕获,我在 WPF 的 App.Dispatch.UnhandledException
事件处理程序中得到它。我从 App.OnStartup
方法调用此方法,如下所示:
protected override async void OnStartup(StartupEventArgs e) {
<...>
var result = await restClient.GetMyroles();
<...>
}
我知道 async void
不是调用异步任务的正确方法,但我必须在启动时做一些,但我的问题是 [=21= 抛出的奇怪 OutOfMemoryException
].如果它确实重要,return API 调用的值是一个 4 字节整数,所以我不会尝试下载 8K 蓝光并且客户端上有足够的内存。
主要观察结果:
- 偶尔会抛出异常
- 仅在应用程序启动时抛出异常(这个特定的 API 端点调用仅在启动时调用一次)
- 如果第一次调用成功,那么所有后续 API 调用都会按预期工作,并且在正常情况下永远不会失败
- 多个客户端报告的问题,唯一常见的是:它们通过 Palo Alto VPN 客户端连接到服务器
- 当客户端直接连接到服务器(没有 VPN)时从未出现问题
- 客户端上有其他应用程序通过同一 VPN 进行通信,它们似乎没有任何 VPN 连接问题。
最后,我找到并解决了问题。实际问题在这里:
Credentials = CredentialCache.DefaultNetworkCredentials
HttpClient 似乎无法读取凭据缓存内容(对此一无所知)。修复方法是将该行替换为:
UseDefaultCredentials = true
一切都开始正常工作了。
我有一个通过 VPN 使用 Web API 的 WPF 桌面应用程序。相关详情:
- 应用程序类型:WPF
- .NET: 4.6.2
- OS: Windows 10
- VPN:帕洛阿尔托 GlobalProtect
有时它会在应用程序启动时从 HttpClient 抛出未捕获的异常:
Error message: Exception of type 'System.OutOfMemoryException' was thrown.
Stack trace:
at MyApp.Net.Services.RestNetClient.<GetMyRoles>d__5.MoveNext()
at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.Start[TStateMachine](TStateMachine& stateMachine)
at MyApp.Net.Services.RestNetClient.GetMyRoles()
<...>
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at MyApp.Wpf.UI.App.<OnStartup>d__3.MoveNext() in <...>\App.xaml.cs:line 44
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
这里是 RestNetClient
class 实现的相关部分
public class RestNetClient : INetClient {
public RestNetClient(IRemoteConfig config) {
if (Client == null) {
HttpClientHandler authHandler = new HttpClientHandler {
Credentials = CredentialCache.DefaultNetworkCredentials
};
Client = new HttpClient(authHandler) { BaseAddress = config.ServerAddress };
Client.DefaultRequestHeaders.Accept.Clear();
Client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
}
protected static HttpClient Client { get; private set; }
public async Task<IServiceOperationResult<AppRole>> GetMyRoles() {
var result = new ServiceOperationResult<AppRole>();
try {
HttpResponseMessage response = await Client.GetAsync("api/ntds/getmyroles");
if (response.IsSuccessStatusCode) {
String json = await response.Content.ReadAsStringAsync();
result.Value = JsonConvert.DeserializeObject<AppRole>(json);
} else {
await response.SetErrorCode(result);
}
} catch (Exception ex) {
result.HResult = ErrorCode.E_INVALIDARG;
result.ErrorMessage = ex.Message;
}
return result;
}
<...>
异常未在 GetMyRoles
方法的 catch
子句中捕获,我在 WPF 的 App.Dispatch.UnhandledException
事件处理程序中得到它。我从 App.OnStartup
方法调用此方法,如下所示:
protected override async void OnStartup(StartupEventArgs e) {
<...>
var result = await restClient.GetMyroles();
<...>
}
我知道 async void
不是调用异步任务的正确方法,但我必须在启动时做一些,但我的问题是 [=21= 抛出的奇怪 OutOfMemoryException
].如果它确实重要,return API 调用的值是一个 4 字节整数,所以我不会尝试下载 8K 蓝光并且客户端上有足够的内存。
主要观察结果:
- 偶尔会抛出异常
- 仅在应用程序启动时抛出异常(这个特定的 API 端点调用仅在启动时调用一次)
- 如果第一次调用成功,那么所有后续 API 调用都会按预期工作,并且在正常情况下永远不会失败
- 多个客户端报告的问题,唯一常见的是:它们通过 Palo Alto VPN 客户端连接到服务器
- 当客户端直接连接到服务器(没有 VPN)时从未出现问题
- 客户端上有其他应用程序通过同一 VPN 进行通信,它们似乎没有任何 VPN 连接问题。
最后,我找到并解决了问题。实际问题在这里:
Credentials = CredentialCache.DefaultNetworkCredentials
HttpClient 似乎无法读取凭据缓存内容(对此一无所知)。修复方法是将该行替换为:
UseDefaultCredentials = true
一切都开始正常工作了。