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

2023-02-24 03:29:28 +08:00
 edis0n0

故障代码:

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

4348 次点击
所在节点    .NET
22 条回复
SMGdcAt4kPPQ
2023-02-24 03:33:33 +08:00
猜一个外部修改闭包内部变量的问题
SMGdcAt4kPPQ
2023-02-24 03:36:18 +08:00
另外可以试试把代码和问题一起贴进 ChatGPT
xiadong1994
2023-02-24 03:38:10 +08:00
试试吧 dsIndex 传进函数里而不要用外部的变量
edis0n0
2023-02-24 03:39:07 +08:00
补张断点变量内容的截图 https://i.ibb.co/cgLd9Sr/2.jpg
@ComputerIdiot #1 操作 dsIndex 的就 for 循环一处呀
SMGdcAt4kPPQ
2023-02-24 03:40:54 +08:00
@edis0n0 就是 for 循环修改了 dsIndex ,解决方法是循环体里 dsIndex 赋值给新变量,原使用 dsIndex 的地方使用新变量
edis0n0
2023-02-24 03:53:59 +08:00
@ComputerIdiot #5 看起来确实是这问题,谢谢
lsk569937453
2023-02-24 07:10:52 +08:00
你这个 Task.factory.startNew 是提交一个任务到线程池里执行。
所以主线程的循环的 dsIndex=2 的时候,即你的 for 循环代码已经执行完的时候,你的异步任务才开始执行。
Goooooos
2023-02-24 08:50:02 +08:00
Java 里,内部类访问外部类的局部变量,需要 final 修饰,能避免出现这种 bug
eraserking
2023-02-24 08:50:57 +08:00
应该把 dsIndex 作为参数传进 StartNew 的那个异步方法,它除了可以接收 Action 也可以 Action<object>的
你这是捕获到的闭包
500
2023-02-24 09:12:02 +08:00
这应该是一个典型的 ”Linq to sql 延迟执行“ 问题
wanei
2023-02-24 09:15:31 +08:00
await
ilingfeng
2023-02-24 09:34:29 +08:00
@lsk569937453 就是这个问题,Task.factory.startNew 只是代表提交一个 Task 任务到线程池,什么时候执行还得等 CPU 调度,不是立马执行
maplefly
2023-02-24 09:36:17 +08:00
把那个 index 传入 task 里面就好了
nothingistrue
2023-02-24 10:04:03 +08:00
我是做 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
2023-02-24 11:06:08 +08:00
for 或者 foreach 里面声明一个变量 j(名称自取),dsIndex 赋值给声明的变量 j ,用 dsIndex 的地方替换成 j
zhy0216
2023-02-24 11:09:40 +08:00
盲猜闭包的问题
你再 for 循环内闭包外再声明一个变量
闭包内用那个变量
timethinker
2023-02-24 12:20:05 +08:00
不用猜,就是闭包引用外部变量的问题,index 的作用域对于所有的闭包都是同一个值,因此当异步执行的时候,闭包内获取的 index 大概率就是同一个值:2 。

解决办法很简单,你把 Task.Factory.StartNew 这里的一块代码提取出来当作一个方法来执行,把 index 当作参数传入。
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
2023-02-24 18:05:52 +08:00
glouhao
2023-02-24 21:04:47 +08:00
费眼睛的直接给 chatgpt 啊

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/918707

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX