本文介绍了parallel.foreach和httpclient-奇怪的行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一段代码循环遍历一个集合,并为每次迭代调用httpclient. httpclient调用的api平均需要30到40毫秒才能执行.顺序调用它,可以得到预期的结果,但是,一旦我使用Parallel.foreach,它会花费更长的时间.仔细查看日志,可以看到相当多的httpclient调用需要花费1000毫秒以上的时间才能执行,然后时间又回落到30-40毫秒.查看api日志,我几乎看不到它超过100毫秒.我不确定为什么会出现这种峰值.

I have a piece of code that loops over a collection and calls httpclient for each iteration. The api that the httpclient calls, takes on average 30-40ms to execute. Calling it sequentially, I get the expected outcome, however as soon as I use Parallel.foreach, it takes longer. Looking closely in the logs, I can see quite a few httpclient calls take more 1000ms to execute and then the time drops back to 30-40ms. Looking in the api logs, I can see it barely goes over 100ms. I am not sure why I get this spike.

代码是

using (var client = new HttpClient())
{
  var content = new StringContent(parameters, Encoding.UTF8, "application/json");
  var response = client.PostAsync(url, content);
  _log.Info(string.Format("Took {0} ms to send post", watch.ElapsedMilliseconds));
  watch.Restart();

  var responseString = response.Result.Content.ReadAsStringAsync();
  _log.Info(string.Format("Took {0} ms to readstring after post", watch.ElapsedMilliseconds));
}

并行调用是这样的

    Console.WriteLine("starting parallel...");
    Parallel.ForEach(recipientCollections, recipientCollection =>
      {
        // A lot of processing happens here to create relevant content
        var secondaryCountryRecipientList = string.Join(",",refinedCountryRecipients);
        var emailApiParams = new SendEmailParametersModel(CountrySubscriberApplicationId,
                                        queueItem.SitecoreId, queueItem.Version, queueItem.Language, countryFeedItem.Subject,
                                        countryFeedItem.Html, countryFeedItem.From, _recipientsFormatter.Format(secondaryCountryRecipientList));

       log.Info(string.Format("Sending email request for {0}. Recipients {1}",                                        queueItem.SitecoreId, secondaryCountryRecipientList));

        var response = _notificationsApi.Invoke(emailApiParams);
        });

谢谢

推荐答案

默认情况下,.NET每个服务器仅允许2个连接.要更改此设置,您必须更改 ServicePointManager.DefaultConnectionLimit 设置为较大的值,例如20或100.

By default .NET allows only 2 connections per server. To change this you have to change the value of ServicePointManager.DefaultConnectionLimit to a larger value, eg 20 or 100.

如果您发出太多请求,这不会阻止服务器泛滥或占用太多内存.更好的选择是使用 ActionBlock< T> 来缓冲请求并在受控功能中并行发送请求,例如:

This won't prevent flooding the server or consuming too much memory if you make too many requests though. A better option would be to use an ActionBlock< T> to buffer requests and send them in parallel in a controlled function, eg:

 ServicePointManager.DefaultConnectionLimit =20;

 var client = new HttpClient();

 var blockOptions=new ExecutionDataflowBlockOptions{MaxDegreeOfParallelism=10};

 var emailBlock=new ActionBlock<SendEmailParametersModel>(async arameters=>
     {
         var watch=new Stopwatch();
         var content = new StringContent(parameters, Encoding.UTF8, "application/json");
         var response = await client.PostAsync(url, content);
         _log.Info(..);
         watch.Restart();

         var responseString = await response.Result.Content.ReadAsStringAsync();
         _log.Info(...);
 });

发送电子邮件不再需要并行调用:

Sending the emails doesn't require parallel invocation any more:

foreach(var recipientCollection in recipientCollections)
{
    var secondaryCountryRecipientList = string.Join(",",refinedCountryRecipients);
    var emailApiParams = new SendEmailParametersModel(CountrySubscriberApplicationId, queueItem.SitecoreId, queueItem.Version, queueItem.Language, countryFeedItem.Subject,countryFeedItem.Html, countryFeedItem.From, _recipientsFormatter.Format(secondaryCountryRecipientList));

   emailBlock.Post(emailApiParams);
   log.Info(...);
}
emailBlock.Complete();
await emailBlock.Completion();

HttpClient是线程安全的,它允许您对所有请求使用相同的客户端.

HttpClient is thread-safe which allows you to use the same client for all requests.

上面的代码将缓冲所有请求,并一次执行10个.调用Complete()告诉该块完成所有操作并停止处理新消息. await emailBlock.Completion()等待所有现有消息完成后再继续

The code above will buffer all requests and execute them 10 at a time. Calling Complete() tells the block to complete everything and stop processing new messages. await emailBlock.Completion() waits for all existing messages to finish before proceeding

这篇关于parallel.foreach和httpclient-奇怪的行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-23 03:36