使用Task.Run()导致调用循环出现意外索引越界问题
问题描述
我有如下C#代码:
public List<bool> scaleActive { get; private set; } //... for (int i = 0; i < scaleActive.Count; i++) { if (scaleActive[i]) { //... Task.Run(() => NewportUpload.SendBasket(localTempPath, string.Format("{0}{1}", basketNamesBuy[i], CSV), tradeBasket, privateKeyFileName, privateKeyPassphrase, instinetHost, instinetUserName, instinetRemotePath)); //... } }
运行时出现以下错误:
System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
调试发现i的值大于scaleActive的Count,这不符合预期。但移除Task.Run()改为同步调用如下代码时则运行正常:
public List<bool> scaleActive { get; private set; } //... for (int i = 0; i < scaleActive.Count; i++) { if (scaleActive[i]) { //... NewportUpload.SendBasket(localTempPath, string.Format("{0}{1}", basketNamesBuy[i], CSV), tradeBasket, privateKeyFileName, privateKeyPassphrase, instinetHost, instinetUserName, instinetRemotePath); //... } }
scaleActive和basketNamesBuy在程序中均未被修改,为何使用Task.Run()时循环会出现索引越界?
问题原因及解决方法
这是C#中闭包捕获循环变量的经典问题:
- 当你在
Task.Run()的lambda表达式中使用变量i时,lambda捕获的是i的引用,而不是当前循环迭代的i的副本。 - 循环本身是同步执行的,速度远快于后台任务的启动和执行。当后台任务真正开始执行
basketNamesBuy[i]或scaleActive[i]时,循环可能已经完成了多次迭代,甚至已经结束,此时i的值已经超过了scaleActive.Count或basketNamesBuy.Count的范围,自然就抛出索引越界异常。 - 同步调用时,代码是按顺序执行的,每次调用
SendBasket时i都是当前迭代的有效值,所以不会出问题。
解决方法很简单,在循环内部创建一个i的副本,让lambda捕获这个副本:
for (int i = 0; i < scaleActive.Count; i++) { if (scaleActive[i]) { // 创建当前迭代的i的副本 int currentIndex = i; //... Task.Run(() => NewportUpload.SendBasket(localTempPath, string.Format("{0}{1}", basketNamesBuy[currentIndex], CSV), tradeBasket, privateKeyFileName, privateKeyPassphrase, instinetHost, instinetUserName, instinetRemotePath)); //... } }
这样每个后台任务都会捕获到对应迭代的currentIndex值,而不是共享同一个i的引用,就不会出现索引越界的问题了。
内容的提问来源于stack exchange,提问作者jgriffo1
相关产品推荐
相关产品推荐

