【问题标题】:HTTP client Get request within a threading parallel pool - random failuresHTTP 客户端在线程并行池中获取请求 - 随机失败
【发布时间】:2021-03-10 19:26:05
【问题描述】:

我有一个 API,可以为我提供一些传感器的状态(位于 Azure 函数中)

www.myapp.com/api/sensors/{sensor_id}

我的客户端应用程序需要每隔一秒检查一次每个传感器!所以在这种情况下,我有 10 个传感器,客户端需要每秒发送 10 个 http get 请求!

我决定使用 Parallel 并从我的方法的每个实例中创建一个池,该实例运行并执行 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 方法中,但不知道为什么它在运行几分钟后发生,而不仅仅是从一开始!

还请注意,我必须通过调用 .Result 来使我的 GetAsync 方法不异步,就好像我确实等待然后应用程序在启动第一个请求之前崩溃!它似乎不喜欢在并行池中被调用。

感谢您的帮助!

更新了更多细节:

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

【问题讨论】:

  • 如何将 HttpClient 注入到您的 HttpClientService 中?以及如何将您的服务添加到 DI?请更新您的问题
  • 完成,谢谢@SaeedEsmaeelinejad
  • 不要使用Thread.Sleep(1000);,而是使用await Task.Delay(1000); 并对其进行测试
  • 好的,马上试试
  • 没用,它经历了一个循环然后就存在了,我认为这是因为在延迟之后它返回一个 Task 并且当 Run 方法得到一个 Task 时,它假定应用程序已完成执行!

标签: c# console-application httpclient .net-5


【解决方案1】:

以下是一些一般准则:

  1. Avoid async void。您的代码目前正在将 async lambdas 添加为 Action 代表,最终成为 async void
  2. Go async all the way。即,不要阻止 async 代码。特别是不要使用.Result

最后,Parallel 不能与async 混合使用,因为Parallel 用于受 CPU 限制的多线程并行,而不是用于受 I/O 限制的非线程异步。您需要的解决方案是异步并发,即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;
    }
  }
}

【讨论】:

  • 非常感谢,我没有意识到异步无效!它现在按预期工作!
猜你喜欢
  • 2013-05-10
  • 1970-01-01
  • 2014-10-13
  • 2020-07-12
  • 2014-08-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多