故障代码:
在开发环境中 DataSources.Count 最大为 2 ,也就是 dsIndex 只有可能是 0,1 ,在 Task.Factory 外下断点拿到的值也确实都是 0,1 ,但在 Task.Factory 里拿到的 dsIndex 却总是 2 ,已经超出了 for 循环结束条件了,难道这个 Task 在循环结束才被开始执行?应该要怎么修改比较好?
1
SMGdcAt4kPPQ 2023-02-24 03:33:33 +08:00 via Android
猜一个外部修改闭包内部变量的问题
|
2
SMGdcAt4kPPQ 2023-02-24 03:36:18 +08:00 via Android
另外可以试试把代码和问题一起贴进 ChatGPT
|
3
xiadong1994 2023-02-24 03:38:10 +08:00
试试吧 dsIndex 传进函数里而不要用外部的变量
|
4
edis0n0 OP |
5
SMGdcAt4kPPQ 2023-02-24 03:40:54 +08:00 via Android 1
@edis0n0 就是 for 循环修改了 dsIndex ,解决方法是循环体里 dsIndex 赋值给新变量,原使用 dsIndex 的地方使用新变量
|
6
edis0n0 OP @ComputerIdiot #5 看起来确实是这问题,谢谢
|
7
lsk569937453 2023-02-24 07:10:52 +08:00 2
你这个 Task.factory.startNew 是提交一个任务到线程池里执行。
所以主线程的循环的 dsIndex=2 的时候,即你的 for 循环代码已经执行完的时候,你的异步任务才开始执行。 |
8
Goooooos 2023-02-24 08:50:02 +08:00
Java 里,内部类访问外部类的局部变量,需要 final 修饰,能避免出现这种 bug
|
9
eraserking 2023-02-24 08:50:57 +08:00 1
应该把 dsIndex 作为参数传进 StartNew 的那个异步方法,它除了可以接收 Action 也可以 Action<object>的
你这是捕获到的闭包 |
10
500 2023-02-24 09:12:02 +08:00
这应该是一个典型的 ”Linq to sql 延迟执行“ 问题
|
11
wanei 2023-02-24 09:15:31 +08:00
await
|
12
ilingfeng 2023-02-24 09:34:29 +08:00 1
@lsk569937453 就是这个问题,Task.factory.startNew 只是代表提交一个 Task 任务到线程池,什么时候执行还得等 CPU 调度,不是立马执行
|
13
maplefly 2023-02-24 09:36:17 +08:00
把那个 index 传入 task 里面就好了
|
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 变量后再传给匿名内部类去使用。 |
15
xcqc 2023-02-24 11:06:08 +08:00
for 或者 foreach 里面声明一个变量 j(名称自取),dsIndex 赋值给声明的变量 j ,用 dsIndex 的地方替换成 j
|
16
zhy0216 2023-02-24 11:09:40 +08:00
盲猜闭包的问题
你再 for 循环内闭包外再声明一个变量 闭包内用那个变量 |
17
timethinker 2023-02-24 12:20:05 +08:00
不用猜,就是闭包引用外部变量的问题,index 的作用域对于所有的闭包都是同一个值,因此当异步执行的时候,闭包内获取的 index 大概率就是同一个值:2 。
解决办法很简单,你把 Task.Factory.StartNew 这里的一块代码提取出来当作一个方法来执行,把 index 当作参数传入。 |
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 |
19
v2bsd 2023-02-24 18:05:52 +08:00
|
20
glouhao 2023-02-24 21:04:47 +08:00 via Android
费眼睛的直接给 chatgpt 啊
|
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 |
22
hez2010 2023-03-10 12:30:43 +08:00 via Android
解决方法也很简单,自己加一个局部变量,不要直接用 for 的循环变量就行了.
for (...) { var j = dsIndex; 然后这里的 Task.Factory 里用 j 而不是 dsIndex } |