C# Parallel.ForEach 和 Task.WhenAll 有时返回的值比预期的要少
C# Parallel.ForEach and Task.WhenAll sometimes returning less values then supposed
我有这个:
Parallel.ForEach(numbers, (number) =>
{
var value = Regex.Replace(number, @"\s+", "%20");
tasks.Add(client.GetAsync(url + value));
});
await Task.WhenAll(tasks).ConfigureAwait(false);
foreach (var task in tasks)
{
...
}
有时 returns 到达 foreach(var task in tasks) 时任务较少,但在几次请求后,开始返回所有任务。
我已将 ConfigureAwait 更改为 true,但有时 returns 任务仍然减少。
顺便说一句,我使用 Parallel.ForEach 因为每个 client.GetAsync(url + 值)都是对外部 api 的请求,其特殊性是其延迟 SLA 低于99% 的请求为 1s
你们能解释一下为什么有时 returns 任务少吗?
有没有办法保证始终返回所有任务?
谢谢
And is there a way to guarantee returning always all tasks?
评论中的几个人指出你应该这样做,假设 numbers
是一个非线程安全的列表:
foreach(var number in numbers)
{
var value = Regex.Replace(number, @"\s+", "%20");
tasks.Add(client.GetAsync(url + value));
}
await Task.WhenAll(tasks).ConfigureAwait(false);
foreach (var task in tasks)
{
...
}
并行创建执行下载的任务似乎没有任何明显的好处;这发生得很快。等待下载完成是在 WhenAll
ps; URL 的数据转义有多种更复杂的方法,但如果您特别希望将任何类型的空白转换为 %20,我想使用正则表达式来完成它是有意义的..
编辑;你问什么时候使用 Parallel ForEach,我会说“通常不要,因为你必须更加小心你使用它的上下文”,但是如果你做了 Parallel.ForEach做更多的同步工作,这可能有意义:
Parallel.ForEach(numbers, number =>
{
var value = Regex.Replace(number, @"\s+", "%20");
var r = client.Get(url + value));
//do something meaningful with r here, i.e. whatever ... is in your foreach (var task in tasks)
});
但请注意,如果您出于协调目的从体内对某些共享事物执行更新,则它需要是线程安全的
你没有展示它,所以我们只能猜测,但我假设 tasks
是 List<>
。此集合类型不是线程安全的;您的并行循环可能会“覆盖”值。执行列表的手动锁定或切换到线程安全集合,例如 ConcurrentQueue<>
var tasks = new ConcurrentQueue<Task<string>>();
Parallel.ForEach(numbers, number =>
{
var value = Regex.Replace(number, @"\s+", "%20");
tasks.Enqueue(client.GetAsync(url + value));
});
await Task.WhenAll(tasks.ToArray()).ConfigureAwait(false);
foreach (var task in tasks)
{
// whatever
}
也就是说,您对 Parallel.ForEach
的使用很可疑。您没有在循环内执行任何真正重要的事情。使用 Parallel
,尤其是在适当锁定的情况下,可能会产生更高的开销,从而抵消您声称观察到的或通过并行化 Regex
调用实现的任何潜在收益。我会将其转换为正常的 foreach
循环并预编译 Regex
以抵消(部分)它的开销:
// in class
private static readonly Regex SpaceRegex = new Regex(@"\s+", RegexOptions.Compiled);
// in method
var tasks = new List<Task<string>>();
foreach (var number in numbers)
{
var value = SpaceRegex.Replace(number, "%20");
tasks.Add(client.GetAsync(url + value));
}
await Task.WhenAll(tasks).ConfigureAwait(false);
foreach (var task in tasks)
{
// whatever
}
或者,根本不使用正则表达式。使用适当的 Uri 转义机制,这将带来修复不仅仅是空格的额外好处:
var value = Uri.EscapeDataString(number);
// or
var fullUri = Uri.EscapeUriString(url + number);
注意那里有两种不同的方法。正确使用取决于 url
和 number
的值。还有其他机制,例如 HttpUtility.UrlEncode
方法...但我 认为 这些是首选方法。
我有这个:
Parallel.ForEach(numbers, (number) =>
{
var value = Regex.Replace(number, @"\s+", "%20");
tasks.Add(client.GetAsync(url + value));
});
await Task.WhenAll(tasks).ConfigureAwait(false);
foreach (var task in tasks)
{
...
}
有时 returns 到达 foreach(var task in tasks) 时任务较少,但在几次请求后,开始返回所有任务。
我已将 ConfigureAwait 更改为 true,但有时 returns 任务仍然减少。
顺便说一句,我使用 Parallel.ForEach 因为每个 client.GetAsync(url + 值)都是对外部 api 的请求,其特殊性是其延迟 SLA 低于99% 的请求为 1s
你们能解释一下为什么有时 returns 任务少吗?
有没有办法保证始终返回所有任务?
谢谢
And is there a way to guarantee returning always all tasks?
评论中的几个人指出你应该这样做,假设 numbers
是一个非线程安全的列表:
foreach(var number in numbers)
{
var value = Regex.Replace(number, @"\s+", "%20");
tasks.Add(client.GetAsync(url + value));
}
await Task.WhenAll(tasks).ConfigureAwait(false);
foreach (var task in tasks)
{
...
}
并行创建执行下载的任务似乎没有任何明显的好处;这发生得很快。等待下载完成是在 WhenAll
ps; URL 的数据转义有多种更复杂的方法,但如果您特别希望将任何类型的空白转换为 %20,我想使用正则表达式来完成它是有意义的..
编辑;你问什么时候使用 Parallel ForEach,我会说“通常不要,因为你必须更加小心你使用它的上下文”,但是如果你做了 Parallel.ForEach做更多的同步工作,这可能有意义:
Parallel.ForEach(numbers, number =>
{
var value = Regex.Replace(number, @"\s+", "%20");
var r = client.Get(url + value));
//do something meaningful with r here, i.e. whatever ... is in your foreach (var task in tasks)
});
但请注意,如果您出于协调目的从体内对某些共享事物执行更新,则它需要是线程安全的
你没有展示它,所以我们只能猜测,但我假设 tasks
是 List<>
。此集合类型不是线程安全的;您的并行循环可能会“覆盖”值。执行列表的手动锁定或切换到线程安全集合,例如 ConcurrentQueue<>
var tasks = new ConcurrentQueue<Task<string>>();
Parallel.ForEach(numbers, number =>
{
var value = Regex.Replace(number, @"\s+", "%20");
tasks.Enqueue(client.GetAsync(url + value));
});
await Task.WhenAll(tasks.ToArray()).ConfigureAwait(false);
foreach (var task in tasks)
{
// whatever
}
也就是说,您对 Parallel.ForEach
的使用很可疑。您没有在循环内执行任何真正重要的事情。使用 Parallel
,尤其是在适当锁定的情况下,可能会产生更高的开销,从而抵消您声称观察到的或通过并行化 Regex
调用实现的任何潜在收益。我会将其转换为正常的 foreach
循环并预编译 Regex
以抵消(部分)它的开销:
// in class
private static readonly Regex SpaceRegex = new Regex(@"\s+", RegexOptions.Compiled);
// in method
var tasks = new List<Task<string>>();
foreach (var number in numbers)
{
var value = SpaceRegex.Replace(number, "%20");
tasks.Add(client.GetAsync(url + value));
}
await Task.WhenAll(tasks).ConfigureAwait(false);
foreach (var task in tasks)
{
// whatever
}
或者,根本不使用正则表达式。使用适当的 Uri 转义机制,这将带来修复不仅仅是空格的额外好处:
var value = Uri.EscapeDataString(number);
// or
var fullUri = Uri.EscapeUriString(url + number);
注意那里有两种不同的方法。正确使用取决于 url
和 number
的值。还有其他机制,例如 HttpUtility.UrlEncode
方法...但我 认为 这些是首选方法。