英文:
Index overflow in for loop with nested thread
问题
我正在编写一个测试工具(一个WinForm),以检查一个网站的性能。为此,我设置了要执行的请求数量,并附带了一组与请求相关的参数列表。我可以将请求设置为并行执行或按顺序执行。
如果按顺序执行一切正常,但如果并行执行,我会遇到一个与for循环相关的奇怪问题。
我知道我可以使用Parallel.For,但因为我正在调查与并行性相关的另一个错误,所以我临时使用了一个常规的for循环,其中包含直接执行或使用Task.Run()的嵌套操作。
以下是有问题的代码:
private void Run()
{
ConcurrentBag<long> callTimes = new ConcurrentBag<long>();
int httpErrors = 0;
int progress = 0;
string uri = txtUrl.Text ?? string.Empty;
if (string.IsNullOrWhiteSpace(uri))
return;
Func<List<string>, int, long> testCall = (p, i) =>
{
try
{
using (var client = new HttpClient())
{
Stopwatch timer = new Stopwatch();
timer.Start();
string actualUrl = string.Format(uri, p.ToArray());
var getTask = client.GetAsync(actualUrl);
getTask.Wait();
timer.Stop();
var result = getTask.Result;
if (result == null || (int)result.StatusCode >= 400)
{
txtErrors.ThreadSafeAppendText($"Connection error {(result?.StatusCode.ToString() ?? "NULL")}'\r\n");
Interlocked.Increment(ref httpErrors);
}
return timer.ElapsedMilliseconds;
}
}
catch (Exception actionErr)
{
txtErrors.ThreadSafeAppendText($"Error while execution callAction {i} with parameters '{string.Join(", ", p)}' : \r\n" + actionErr.Message);
}
return -1;
};
try
{
List<List<string>> parameters = this.ParseParameters();
int parametersCount = parameters.Count;
int executions = (int)updRequests.Value;
//used to randomly access parameters in a way suitable also for the parallel scenario (i precompute all the random number i need while parallel processing is not yet started)
Random rng = new Random();
List<int> randoms = new List<int>();
for (int i = 0; i < executions; i++)
randoms.Add(rng.Next(0, parametersCount));
//randoms.Count is guaranteed to be equal to executions
for (int index = 0; index < executions; index++)
{
Action parallelAction = () =>
{
int currentIndex = index;
List<string> currentParameter = parameters[randoms[currentIndex] % parametersCount]; //<<--- strange overflow here currentIndex >= executions
callTimes.Add(testCall(currentParameter, currentIndex));
Interlocked.Increment(ref progress);
if (progress % 10 == 0)
prbProgress.ThreadSafeAction(this.RefreshProgressBar, progress, executions);
};
if (chkParallelExecution.Checked)
Task.Run(parallelAction);
else
parallelAction();
}
this.Reporting(callTimes, httpErrors);
}
catch (Exception err)
{
txtErrors.ThreadSafeAppendText($"Error while running stress test : \r\n" + err.Message);
}
}
我不理解的奇怪之处是,变量currentIndex
如何变得大于或等于executions
,因为只有循环操作这两个变量,应该强制执行相反的情况。所以我认为我在理解并行处理的方式方面可能有所遗漏。
英文:
I am writing a testing utility (a WinForm) to check how a web site perform. To do so i set a number of request to make, with a list of parameters associated with requests. I can set the requests to happen in parallel or in sequence.
If i work in sequence everything is fine, but if i work in parallel i get a strange issue with the for loop.
I know i may use Parallel.For but because i am investigating another bug related to parallelism i temporary used a regualr for, with a nested action executed directly or with a Task.Run().
Here the problematic code :
private void Run()
{
ConcurrentBag<long> callTimes = new ConcurrentBag<long>();
int httpErrors = 0;
int progress = 0;
string uri = txtUrl.Text ?? string.Empty;
if (string.IsNullOrWhiteSpace(uri))
return;
Func<List<string>,int,long> testCall = (p,i) =>
{
try
{
using (var client = new HttpClient())
{
Stopwatch timer = new Stopwatch();
timer.Start();
string actualUrl = string.Format(uri, p.ToArray());
var getTask = client.GetAsync(actualUrl);
getTask.Wait();
timer.Stop();
var result = getTask.Result;
if (result == null || (int)result.StatusCode >= 400)
{
txtErrors.ThreadSafeAppendText($"Connection error {(result?.StatusCode.ToString() ?? "NULL")}'\r\n");
Interlocked.Increment(ref httpErrors);
}
return timer.ElapsedMilliseconds;
}
}
catch ( Exception actionErr)
{
txtErrors.ThreadSafeAppendText($"Error while execution callAction {i} with parameters '{string.Join(", " , p)}' : \r\n" + actionErr.Message);
}
return -1;
};
try
{
List<List<string>> parameters = this.ParseParameters();
int parametersCount = parameters.Count;
int executions = (int)updRequests.Value;
//used to randomly access parameters in a way suitable also for the parallel scenario (i precompute all the random number i need while parallel processing is not yet started)
Random rng = new Random();
List<int> randoms = new List<int>();
for (int i = 0; i < executions; i++)
randoms.Add(rng.Next(0, parametersCount));
//randoms.Count is guaranteed to be equal to executions
for ( int index = 0; index < executions; index++)
{
Action parallelAction = () =>
{
int currentIndex = index;
List<string> currentParameter = parameters[randoms[currentIndex] % parametersCount]; //<<--- strange overflow here currentIndex >= executions
callTimes.Add(testCall(currentParameter, currentIndex));
Interlocked.Increment(ref progress);
if (progress % 10 == 0)
prbProgress.ThreadSafeAction(this.RefreshProgressBar, progress, executions);
};
if (chkParallelExecution.Checked)
Task.Run(parallelAction);
else
parallelAction();
}
this.Reporting(callTimes, httpErrors);
}
catch (Exception err)
{
txtErrors.ThreadSafeAppendText($"Error while running stress test : \r\n" + err.Message);
}
}
The strange thing i don't understand is how the variable called currentIndex become >= executions variable, because only the loop manipulate those two variable and should enforce the opposite.
So i think i am missing something in my understanding on how parallel processing happen here.
答案1
得分: 1
有一个相当知名的问题与capturing loop variables有关。
所以你可能应该这样写
for (int index = 0; index < executions; index++)
{
int currentIndex = index;
Action parallelAction = () =>
{
...
另一个可能的问题:
parameters[randoms[currentIndex] % parametersCount];
你永远不知道randoms
中的值都可能是零。你确定不想创建一个0..executions的数组,然后进行shuffle吗?
但是我无法看到这个示例应该失败的明显原因。但是如果你进行一些调试,与索引等相关的错误应该是相当明显的。
英文:
There is a fairly well known issue with capturing loop variables.
So you should probably write
for ( int index = 0; index < executions; index++)
{
int currentIndex = index;
Action parallelAction = () =>
{
...
Another possible issue:
parameters[randoms[currentIndex] % parametersCount];
For all you know the values in randoms
could all be zero. Are you sure you don't want to create an array of 0..executions, and shuffle this instead?
I can't see any obvious reasons why this example should fail however. But errors with indices etc should be rather obvious if you do some debugging.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论