HTTP 客户端在线程并行池中获取请求 - 随机失败
HTTP client Get request within a threading parallel pool - random failures
我有一个 API,它提供了一些传感器的状态(位于 Azure 功能上)
www.myapp.com/api/sensors/{sensor_id}
我的客户端应用程序需要每一秒检查一次每个传感器!所以在这种情况下,我有 10 个传感器,客户端需要每秒发送 10 个 http get 请求!
我决定使用 Parallel 来完成它,并从我的方法的每个实例中创建一个池,运行s 执行 Get 请求!
这是编排每个传感器并将其添加到池中并调用它们的主要方法
public override Task Run(List<int> sensors)
{
try
{
List<Action> actions = new List<Action>();
foreach (var prd in sensors)
{
actions.Add(async () => await Process(prd));
}
ParallelOptions parallelOptions = new ParallelOptions
{
MaxDegreeOfParallelism = 20
};
Parallel.Invoke(parallelOptions, actions.ToArray());
}
catch (Exception ex)
{
Run(sensors);
}
return Task.CompletedTask;
}
在 Process() 方法中,我获取传感器 ID 并将其添加到 API 端点,然后 运行 每 1 秒发送一次 Get 请求。
private async Task<string> Process(int sensorId)
{
while (true)
{
Thread.Sleep(1000);
try
{
SensorModel response = _httpClientService.GetAsync<SensorModel>(string.Format(apiUrl, sensorId)).Result;
if (response != null)
{
if (response.Status == "success")
{
.
.
.
}
_logger.LogError($"API connection unsuccesful...");
return null;
}
_logger.LogError($"API connection failed...");
return null;
}
catch (Exception ex)
{
_logger.LogError($"Exception in retrieving sensor data {StringExtensions.GetCurrentMethod()} {ex}");
return null;
}
}
}
和我的 HttpClient 工厂中的 GetAsync
public async Task<T> GetAsync<T>(string uri)
{
string responseData = null;
try
{
var response = await _client.GetAsync(uri);
responseData = await response.Content.ReadAsStringAsync();
if (!string.IsNullOrEmpty(responseData) && response.StatusCode == HttpStatusCode.OK)
return JsonConvert.DeserializeObject<T>(responseData);
return default;
}
catch (Exception ex)
{
_logger.LogError(ex, responseData);
return default;
}
}
现在上面的代码大部分时间都可以正常工作,但有时它只是开始使连接失败并返回下面的异常,我能修复它的唯一方法是重新启动应用程序。不确定它是否会与大量请求以及它们被同时处理的事实混淆
System.ArgumentNullException: Value cannot be null. (Parameter 'obj')
at System.OrdinalIgnoreCaseComparer.GetHashCode(String obj)
at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
at System.Net.Http.Headers.HttpHeaders.AddHeaders(HttpHeaders sourceHeaders)
at System.Net.Http.Headers.HttpRequestHeaders.AddHeaders(HttpHeaders sourceHeaders)
at System.Net.Http.HttpClient.SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.GetAsync(String requestUri)
at SensorMonitor.Services.Implementations.HttpClientService.GetAsync[T](String uri)
我知道它在我的 GetAsync 方法中,但无法弄清楚为什么它会在 运行ning 几分钟后发生,而不是从一开始就发生!
另请注意,我必须通过调用 .Result 来使我的 GetAsync 方法不异步,就好像我确实 await 然后应用程序在启动第一个请求之前就崩溃了!它似乎不喜欢在并行池中被调用。
非常感谢你的帮助!
更新了更多细节:
HttpClientService.cs
public class HttpClientService : IHttpClientService
{
private readonly HttpClient _client;
private readonly ILogger<HttpClientService> _logger;
private static readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
public HttpClientService(HttpClient client, ILogger<HttpClientService> logger)
{
_client = client;
_logger = logger;
}
.
.
.
这就是我注册 HttpClient 的方式
services.AddHttpClient<IHttpClientService, HttpClientService>();
以下是一些一般准则:
- Avoid
async void
。您的代码当前正在添加 async
lambdas 作为 Action
代表,最终成为 async void
.
- Go
async
all the way。即,不要阻塞 async
代码。特别是,不要使用 .Result
.
最后,Parallel
不会与 async
混用,因为 Parallel
是针对 CPU 绑定的多线程并行性,而不是针对 I/O-bound非线程异步。您需要的解决方案是 异步 并发,即 Task.WhenAll
.
public override async Task Run(List<int> sensors)
{
var tasks = sensors
.Select(prd => Process(prd))
.ToList();
await Task.WhenAll(tasks);
}
private async Task<string> Process(int sensorId)
{
while (true)
{
await Task.Delay(1000);
try
{
SensorModel response = await _httpClientService.GetAsync<SensorModel>(string.Format(apiUrl, sensorId));
if (response != null)
{
if (response.Status == "success")
{
...
}
_logger.LogError($"API connection unsuccesful...");
return null;
}
_logger.LogError($"API connection failed...");
return null;
}
catch (Exception ex)
{
_logger.LogError($"Exception in retrieving sensor data {StringExtensions.GetCurrentMethod()} {ex}");
return null;
}
}
}
我有一个 API,它提供了一些传感器的状态(位于 Azure 功能上)
www.myapp.com/api/sensors/{sensor_id}
我的客户端应用程序需要每一秒检查一次每个传感器!所以在这种情况下,我有 10 个传感器,客户端需要每秒发送 10 个 http get 请求!
我决定使用 Parallel 来完成它,并从我的方法的每个实例中创建一个池,运行s 执行 Get 请求!
这是编排每个传感器并将其添加到池中并调用它们的主要方法
public override Task Run(List<int> sensors)
{
try
{
List<Action> actions = new List<Action>();
foreach (var prd in sensors)
{
actions.Add(async () => await Process(prd));
}
ParallelOptions parallelOptions = new ParallelOptions
{
MaxDegreeOfParallelism = 20
};
Parallel.Invoke(parallelOptions, actions.ToArray());
}
catch (Exception ex)
{
Run(sensors);
}
return Task.CompletedTask;
}
在 Process() 方法中,我获取传感器 ID 并将其添加到 API 端点,然后 运行 每 1 秒发送一次 Get 请求。
private async Task<string> Process(int sensorId)
{
while (true)
{
Thread.Sleep(1000);
try
{
SensorModel response = _httpClientService.GetAsync<SensorModel>(string.Format(apiUrl, sensorId)).Result;
if (response != null)
{
if (response.Status == "success")
{
.
.
.
}
_logger.LogError($"API connection unsuccesful...");
return null;
}
_logger.LogError($"API connection failed...");
return null;
}
catch (Exception ex)
{
_logger.LogError($"Exception in retrieving sensor data {StringExtensions.GetCurrentMethod()} {ex}");
return null;
}
}
}
和我的 HttpClient 工厂中的 GetAsync
public async Task<T> GetAsync<T>(string uri)
{
string responseData = null;
try
{
var response = await _client.GetAsync(uri);
responseData = await response.Content.ReadAsStringAsync();
if (!string.IsNullOrEmpty(responseData) && response.StatusCode == HttpStatusCode.OK)
return JsonConvert.DeserializeObject<T>(responseData);
return default;
}
catch (Exception ex)
{
_logger.LogError(ex, responseData);
return default;
}
}
现在上面的代码大部分时间都可以正常工作,但有时它只是开始使连接失败并返回下面的异常,我能修复它的唯一方法是重新启动应用程序。不确定它是否会与大量请求以及它们被同时处理的事实混淆
System.ArgumentNullException: Value cannot be null. (Parameter 'obj')
at System.OrdinalIgnoreCaseComparer.GetHashCode(String obj)
at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
at System.Net.Http.Headers.HttpHeaders.AddHeaders(HttpHeaders sourceHeaders)
at System.Net.Http.Headers.HttpRequestHeaders.AddHeaders(HttpHeaders sourceHeaders)
at System.Net.Http.HttpClient.SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.GetAsync(String requestUri)
at SensorMonitor.Services.Implementations.HttpClientService.GetAsync[T](String uri)
我知道它在我的 GetAsync 方法中,但无法弄清楚为什么它会在 运行ning 几分钟后发生,而不是从一开始就发生!
另请注意,我必须通过调用 .Result 来使我的 GetAsync 方法不异步,就好像我确实 await 然后应用程序在启动第一个请求之前就崩溃了!它似乎不喜欢在并行池中被调用。
非常感谢你的帮助!
更新了更多细节:
HttpClientService.cs
public class HttpClientService : IHttpClientService
{
private readonly HttpClient _client;
private readonly ILogger<HttpClientService> _logger;
private static readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
public HttpClientService(HttpClient client, ILogger<HttpClientService> logger)
{
_client = client;
_logger = logger;
}
.
.
.
这就是我注册 HttpClient 的方式
services.AddHttpClient<IHttpClientService, HttpClientService>();
以下是一些一般准则:
- Avoid
async void
。您的代码当前正在添加async
lambdas 作为Action
代表,最终成为async void
. - Go
async
all the way。即,不要阻塞async
代码。特别是,不要使用.Result
.
最后,Parallel
不会与 async
混用,因为 Parallel
是针对 CPU 绑定的多线程并行性,而不是针对 I/O-bound非线程异步。您需要的解决方案是 异步 并发,即 Task.WhenAll
.
public override async Task Run(List<int> sensors)
{
var tasks = sensors
.Select(prd => Process(prd))
.ToList();
await Task.WhenAll(tasks);
}
private async Task<string> Process(int sensorId)
{
while (true)
{
await Task.Delay(1000);
try
{
SensorModel response = await _httpClientService.GetAsync<SensorModel>(string.Format(apiUrl, sensorId));
if (response != null)
{
if (response.Status == "success")
{
...
}
_logger.LogError($"API connection unsuccesful...");
return null;
}
_logger.LogError($"API connection failed...");
return null;
}
catch (Exception ex)
{
_logger.LogError($"Exception in retrieving sensor data {StringExtensions.GetCurrentMethod()} {ex}");
return null;
}
}
}