接手屎山遇到了一个 for 循环内创建的 Task 取到的值总是超出 for 循环的结束条件的问题,研究了 6 个小时还没解决,请大佬帮忙看看 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
edis0n0
V2EX    .NET

接手屎山遇到了一个 for 循环内创建的 Task 取到的值总是超出 for 循环的结束条件的问题,研究了 6 个小时还没解决,请大佬帮忙看看

  •  
  •   edis0n0 2023-02-24 03:29:28 +08:00 4769 次点击
    这是一个创建于 961 天前的主题,其中的信息可能已经有所发展或是发生改变。

    故障代码:

    在开发环境中 DataSources.Count 最大为 2 ,也就是 dsIndex 只有可能是 0,1 ,在 Task.Factory 外下断点拿到的值也确实都是 0,1 ,但在 Task.Factory 里拿到的 dsIndex 却总是 2 ,已经超出了 for 循环结束条件了,难道这个 Task 在循环结束才被开始执行?应该要怎么修改比较好?

    22 条回复    2023-03-10 12:30:43 +08:00
    SMGdcAt4kPPQ
        1
    SMGdcAt4kPPQ  
       2023-02-24 03:33:33 +08:00 via Android
    猜一个外部修改闭包内部变量的问题
    SMGdcAt4kPPQ
        2
    SMGdcAt4kPPQ  
       2023-02-24 03:36:18 +08:00 via Android
    另外可以试试把代码和问题一起贴进 ChatGPT
    trn4
        3
    trn4  
       2023-02-24 03:38:10 +08:00
    试试吧 dsIndex 传进函数里而不要用外部的变量
    edis0n0
        4
    edis0n0  
    OP
       2023-02-24 03:39:07 +08:00
    补张断点变量内容的截图 https://i.ibb.co/cgLd9Sr/2.jpg
    @ComputerIdiot #1 操作 dsIndex 的就 for 循环一处呀
    SMGdcAt4kPPQ
        5
    SMGdcAt4kPPQ  
       2023-02-24 03:40:54 +08:00 via Android   1
    @edis0n0 就是 for 循环修改了 dsIndex ,解决方法是循环体里 dsIndex 赋值给新变量,原使用 dsIndex 的地方使用新变量
    edis0n0
        6
    edis0n0  
    OP
       2023-02-24 03:53:59 +08:00
    @ComputerIdiot #5 看起来确实是这问题,谢谢
    lsk569937453
        7
    lsk569937453  
       2023-02-24 07:10:52 +08:00   2
    你这个 Task.factory.startNew 是提交一个任务到线程池里执行。
    所以主线程的循环的 dsIndex=2 的时候,即你的 for 循环代码已经执行完的时候,你的异步任务才开始执行。
    Goooooos
        8
    Goooooos  
       2023-02-24 08:50:02 +08:00
    Java 里,内部类访问外部类的局部变量,需要 final 修饰,能避免出现这种 bug
    eraserking
        9
    eraserking  
       2023-02-24 08:50:57 +08:00   1
    应该把 dsIndex 作为参数传进 StartNew 的那个异步方法,它除了可以接收 Action 也可以 Action<object>的
    你这是捕获到的闭包
    y77FXoxF970725SJ
        10
    y77FXoxF970725SJ  
       2023-02-24 09:12:02 +08:00
    这应该是一个典型的 ”Linq to sql 延迟执行“ 问题
    wanei
        11
    wanei  
       2023-02-24 09:15:31 +08:00
    await
    ilingfeng
        12
    ilingfeng  
       2023-02-24 09:34:29 +08:00   1
    @lsk569937453 就是这个问题,Task.factory.startNew 只是代表提交一个 Task 任务到线程池,什么时候执行还得等 CPU 调度,不是立马执行
    maplefly
        13
    maplefly  
       2023-02-24 09:36:17 +08:00
    把那个 index 传入 task 里面就好了
    nothingistrue
        14
    nothingistrue  
       2023-02-24 10:04:03 +08:00   2
    我是做 java 的,语言不同,不过思想差不多。Task.Factory.StartNew(asycn ...),这是提交了个异步的任务,那它里面的执行时机就不确定了。你的猜测没错,这外面循环就 2 次,所以超大的概率,是循环结束后 Task 才开始执行。如果你的循环是 100 次的话,那么就有可能外面循环到 50 次的时候,第 30 次提交的 Task 开始执行。

    断点调试的时候,之所以你没看出来问题,是因为你打了断点把异步任务给强行变回了同步任务。你打了两个断点,for 循环那里给主任务打了断点,看变量值那一行给 Task 打了断点,这样 Task 的执行时机,被人为控制的跟主任务同步了。

    你的期望是,Task 里面的 dsIndex ,应该是 Task 提交时候的 dsIndex ,这样它就不能用主任务的 dsIndex 变量,因为后者是会被主任务更改值的。解决方法很简单,Task 里面不要直接使用 dsIndex 变量。final int dsIndexFinal = dsIndex ,Task 里面使用 dsIndexFinal 代替 dsIndex 即可。

    @Goooooos java final 并不能避免这种 bug 。你可以试试这段代码:
    final AtomicInteger a = new AtomicInteger(1);
    executorService.submit(()->{
    Thread.sleep(5000);
    System.out.println(a.get());
    });
    System.out.println(a.addAndGet(10));

    像楼主这样的问题确实能避免,不过那是因为像 int dsIndex 这样的基本类型变量,压根就不能直接传递给匿名内部类,然后 IDE 会提醒你把它赋值给一个新的 final Integer 或 final AtomicInteger 变量后再传给匿名内部类去使用。
    xcqc
        15
    xcqc  
       2023-02-24 11:06:08 +08:00
    for 或者 foreach 里面声明一个变量 j(名称自取),dsIndex 赋值给声明的变量 j ,用 dsIndex 的地方替换成 j
    zhy0216
        16
    zhy0216  
       2023-02-24 11:09:40 +08:00
    盲猜闭包的问题
    你再 for 循环内闭包外再声明一个变量
    闭包内用那个变量
    timethinker
        17
    timethinker  
       2023-02-24 12:20:05 +08:00
    不用猜,就是闭包引用外部变量的问题,index 的作用域对于所有的闭包都是同一个值,因此当异步执行的时候,闭包内获取的 index 大概率就是同一个值:2 。

    解决办法很简单,你把 Task.Factory.StartNew 这里的一块代码提取出来当作一个方法来执行,把 index 当作参数传入。
    nekochyan
        18
    nekochyan  
       2023-02-24 14:24:23 +08:00
    for (var i =0 ; i < 10; i++) {
    setTimeout(()=>{
    console.log(i);
    },0)
    }
    如果你会 js 的话会很明显的发现跟这段代码差不多的问题,输出的 i 全部是 10
    v2bsd
        19
    v2bsd  
       2023-02-24 18:05:52 +08:00
    glouhao
        20
    glouhao  
       2023-02-24 21:04:47 +08:00 via Android
    费眼睛的直接给 chatgpt 啊
    hez2010
        21
    hez2010  
       2023-03-10 12:28:13 +08:00 via Android
    很经典的错误
    dsIndex 在被捕获进 Task.Factory 里的 lambda 时被提升到堆了,并且生命周期被延长,Task.Factory 获得的实际上是对 dsIndex 的引用 (如果不这么做的话你就没法在闭包内修改外部的局部变量)
    所以当 Task.Factory 里面的代码被真正执行的时候,你外面的 for 循环已经把 dsIndex 加到 2 然后退出循环了,自然闭包里面拿到的值就会是 2
    hez2010
        22
    hez2010  
       2023-03-10 12:30:43 +08:00 via Android
    解决方法也很简单,自己加一个局部变量,不要直接用 for 的循环变量就行了.
    for (...) {
    var j = dsIndex;
    然后这里的 Task.Factory 里用 j 而不是 dsIndex
    }
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2732 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 08:31 PVG 16:31 LAX 01:31 JFK 04:31
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86