ArgumentOutOfRangeException Using Tasks
本文关键字:Tasks Using ArgumentOutOfRangeException | 更新日期: 2023-09-27 18:35:31
当我真的不确定为什么时,我得到了一个ArgumentOutOfRangeException
。
Task[] downloadTasks = new Task[music.Count];
for (int i = 0; i < music.Count; i++)
downloadTasks[i] = Task.Factory.StartNew(() => DownloadAudio(music[i], lstQueue.Items[i]));
Task.Factory.ContinueWhenAll(downloadTasks, (tasks) =>
{
MessageBox.Show("All the downloads have completed!",
"Success",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
});
当for loop
运行时发生错误i = 1
,我不确定为什么当我肯定music.Count = 1
时它会这样做.
我一直尝试这种方法作为for loop
的替代方案,并得到了相同的例外:
int index = 0;
foreach (MusicFile song in music)
{
downloadTasks[index] = Task.Factory.StartNew(() => DownloadAudio(song, lstQueue.Items[index]));
index++;
}
上面的代码中是否有任何可能导致这种情况的内容?
我也不确定这是否相关,但是当我可以使用线程完成同样的事情时,没有任何例外。只有当我尝试实现任务时,才出现了此异常。
发生这种情况是因为您StartNew
传递 Lambda 表达式,该表达式隐式捕获您的i
变量。这种效果称为闭包。
为了获得正确的行为,您必须创建索引的本地副本:
for (int i = 0; i < music.Count; i++)
{
var currentIndex = i;
downloadTasks[i] = Task.Factory.StartNew(() =>
DownloadAudio(music[currentIndex],
lstQueue.Items[currentIndex]));
}
在这两种情况下,您都在关闭第一个示例中的循环变量i
,或者在第二个示例中关闭手动分配的index
。
正在发生的事情是,i / index
的最终值在循环完成后使用,即当i++
已经增加到超过迭代数组的大小时。(另见此处)
要么根据@Yuval使用附加变量在循环中捕获 i 的值,要么查看将两个集合耦合在一起的方法,这样您就不需要独立迭代music
和lstQueue
,例如,在这里我们将两个集合预先组合成一个新的匿名类:
var musicQueueTuples = music.Zip(lstQueue, (m, q) => new {Music = m, QueueItem = q})
.ToList();
// Which now allows us to use LINQ to project the tasks:
var downloadTasks = musicQueueTuples.Select(
mqt => Task.Factory.StartNew(
() => DownloadAudio(mqt.Music, mqt.QueueItem))).ToArray();
Task.Factory.ContinueWhenAll(downloadTasks, (tasks) => ...
闭
包是你的问题,其中变量 i 被 lambada 表达式引用,因此它可以访问 i 并始终直接从内存中读取其值。
您可以创建用于创建任务处理程序的工厂函数。您可以按照以下思路来解决问题。
private Action CreateTaskHandler(int arg1)
{
return () => DownloadAudio(music[arg1], lstQueue.Items[arg1])
}
Task[] downloadTasks = new Task[music.Count];
for (int i = 0; i < music.Count; i++)
downloadTasks[i] = Task.Factory.StartNew(CreateTaskHandler(i));
}