C# ArgumentOutOfRangeException使用任务

C# ArgumentOutOfRangeException使用任务,c#,.net,task-parallel-library,task,C#,.net,Task Parallel Library,Task,当我真的不知道为什么时,我得到了一个ArgumentOutofRange异常 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.ContinueWhen

当我真的不知道为什么时,我得到了一个ArgumentOutofRange异常

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);
});
上述代码中是否有可能导致这种情况


我也不确定这是否相关,但是当我可以毫无例外地使用线程完成同样的事情时。只有当我尝试实现任务时,才会出现此异常。

发生这种情况是因为您正在传递StartNew a,它隐式地捕获您的I变量。这种效应称为

为了获得正确的行为,您必须制作索引的本地副本:

for (int i = 0; i < music.Count; i++)
{
    var currentIndex = i;
    downloadTasks[i] = Task.Factory.StartNew(() => 
                                             DownloadAudio(music[currentIndex],
                                             lstQueue.Items[currentIndex]));
}

发生这种情况是因为您正在传递StartNew a,它隐式地捕获您的i变量。这种效应称为

为了获得正确的行为,您必须制作索引的本地副本:

for (int i = 0; i < music.Count; i++)
{
    var currentIndex = i;
    downloadTasks[i] = Task.Factory.StartNew(() => 
                                             DownloadAudio(music[currentIndex],
                                             lstQueue.Items[currentIndex]));
}

在这两种情况下,第一个示例中您都是i,第二个示例中您是手动分配的索引

所发生的事情是在循环完成后使用i/index的最终值,也就是当i++的增量超过迭代数组的大小时。看

根据@Yuval,使用附加变量捕获循环中i的值,或者,研究将两个集合耦合在一起的方法,这样您就不需要独立地迭代音乐和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,第二个示例中您是手动分配的索引

所发生的事情是在循环完成后使用i/index的最终值,也就是当i++的增量超过迭代数组的大小时。看

根据@Yuval,使用附加变量捕获循环中i的值,或者,研究将两个集合耦合在一起的方法,这样您就不需要独立地迭代音乐和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) => ...

闭包是您的问题,其中lambada表达式引用变量i,因此它可以访问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));
}

闭包是您的问题,其中lambada表达式引用变量i,因此它可以访问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));
}

虽然这确实解决了上述问题,但现在我在尝试访问lstQueue.Items时遇到了InvalidOperationException。这是因为lstQueue可能是一个UI对象,您尝试从后台线程访问它,但这是不允许的。它包含什么?它是一个保存下载进度百分比的列表视图。我试图同时更新每次下载的进度。但我会想办法做我想做的事。谢谢大家!@米纳托接着调查。这正是您所需要的。虽然这确实解决了上述问题,但现在我收到了一个尝试访问lstQueue.Items的InvalidOperationException。这是因为lstQueue可能是一个UI对象,您尝试从后台线程访问它,但这是不允许的。它包含什么?它是一个保存下载进度百分比的列表视图。我试图同时更新每次下载的进度。但我会想办法做我想做的事。谢谢大家!@米纳托接着调查。这正是你需要的。