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>();

以下是一些一般准则:

  1. Avoid async void。您的代码当前正在添加 async lambdas 作为 Action 代表,最终成为 async void.
  2. 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;
    }
  }
}