You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

嵌套线程场景下for循环的索引溢出问题求助

多线程循环索引溢出问题排查

问题场景

开发WinForm网站性能测试工具,支持串行/并行执行请求,串行执行正常,但并行执行时出现for循环索引溢出。采用常规for循环结合Task.Run()执行嵌套Action,暂未使用Parallel.For。

问题代码

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;

        //预计算随机数,避免并行时的随机问题
        Random rng = new Random();
        List<int> randoms = new List<int>();
        for (int i = 0; i < executions; i++)
            randoms.Add(rng.Next(0, parametersCount));

        for ( int index = 0; index < executions; index++)
        {
            Action parallelAction = () =>
            {
                int currentIndex = index;
                List<string> currentParameter = parameters[randoms[currentIndex] % parametersCount]; //<<--- 此处出现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);
    }
}

异常原因分析

问题核心是闭包对循环变量index的引用捕获机制:

  • 主线程的循环执行速度远快于Task.Run()启动的后台任务,循环很快就会走完,index最终会变成executions(循环终止条件是index < executions,结束时index等于executions)。
  • 你在Action内部写的int currentIndex = index;并没有解决问题,因为这个赋值操作是在后台Task执行时才会触发,此时主线程的循环早已完成,index的值已经超出了合法范围。
  • 所有后台Task捕获的是同一个index变量的引用,而非循环迭代时的当前值,当Task最终执行时,读取到的index可能已经是executions,导致currentIndex >= executions的溢出错误。

解决方案

在循环内部创建局部临时变量保存当前index的值,让闭包捕获这个独立的临时变量(每个循环迭代都会生成一个新的临时变量),修改循环部分代码如下:

for ( int index = 0; index < executions; index++)
{
    // 关键:在循环内部创建局部变量,保存当前迭代的index值
    int loopIndex = index;
    Action parallelAction = () =>
    {
        int currentIndex = loopIndex;
        List<string> currentParameter = parameters[randoms[currentIndex] % parametersCount];
        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();
}

每个Task捕获的是独立的loopIndex变量,其值固定为对应循环迭代时的index,不会被后续循环修改,彻底避免索引溢出问题。

内容的提问来源于stack exchange,提问作者Skary

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 21:55:58