parallel.foreach 和 httpclient - 奇怪的行为
本文关键字:foreach httpclient parallel | 更新日期: 2023-09-27 17:55:08
我有一段代码循环访问集合并为每次迭代调用httpclient。httpclient 调用的 api 平均需要 30-40 毫秒才能执行。按顺序调用它,我得到了预期的结果,但是一旦我使用 Parallel.foreach,它就需要更长的时间。仔细查看日志,我可以看到相当多的 httpclient 调用需要更多 1000 毫秒才能执行,然后时间回落到 30-40 毫秒。查看 api 日志,我可以看到它几乎没有超过 100 毫秒。我不确定为什么我会得到这个峰值。
代码是
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。
但是,如果您发出太多请求,这不会防止服务器泛滥或消耗太多内存。更好的选择是使用 ActionBlock
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(...);
});
发送电子邮件不再需要并行调用:
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
是线程安全的,允许您对所有请求使用相同的客户端。
上面的代码将缓冲所有请求,一次执行 10 个请求。调用Complete()
告诉块完成所有内容并停止处理新消息。 await emailBlock.Completion()
等待所有现有消息完成,然后再继续
您正在使服务器过载。 Parallel
不知道有多少线程最适合您的特定 Web 服务。你会得到不稳定的结果。事实上,如果循环运行很长时间,线程数可能会上升到数百和数千(真的!凭经验确定正确的DOP并固定DOP。
当服务过载时,看到非常高的服务时间并不罕见。不然怎么可能?没有足够的容量来快速完成。
var responseString = response.Result.Content.ReadAsStringAsync()
在这里,您错过了一个.Result
电话。目前时间不对,但这并没有改变结论。
您还可能达到 HTTP 调用的 .NET 并发请求限制。默认值为 2。