【问题标题】:ArgumentOutOfRangeException Using TasksArgumentOutOfRangeException 使用任务
【发布时间】:2014-12-28 07:06:34
【问题描述】:

当我真的不确定为什么时,我收到了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 loopi = 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++;
}

上面的代码中是否有任何可能导致这种情况的地方?

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

【问题讨论】:

    标签: c# .net task-parallel-library task


    【解决方案1】:

    发生这种情况是因为您传递了StartNew 一个Lambda Expression,它隐式地捕获了您的i 变量。这个效果叫做Closure

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

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

    【讨论】:

    • 虽然这确实解决了上述问题,但现在我收到了一个 InvalidOperationException 以尝试访问 lstQueue.Items
    • 那是因为lstQueue 可能是一个 UI 对象,您正试图从后台线程访问它(并且是不允许的)。它包含什么?
    • 这是一个ListView,用于保存下载的进度百分比。我正在尝试同时更新每个下载的进度。但我会弄清楚如何做我想做的事。谢谢!
    • @Minato 然后查看Progress&lt;T&gt;。完全满足您的需求。
    【解决方案2】:

    在这两种情况下,您在第一个示例中是closing over the loop variable i,或者在第二个示例中是您手动分配的index

    发生的情况是i / index 的最终值在循环完成后使用,即i++ 的增量超出了迭代数组的大小。 (见also here

    根据@Yuval 使用附加变量在循环内捕获 i 的值,或者查看将两个集合耦合在一起的方法,这样您就不需要独立迭代 musiclstQueue ,例如这里我们将这两个集合预先组合成一个新的匿名类:

    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) => ...
    

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-03-08
        • 1970-01-01
        • 1970-01-01
        • 2019-11-30
        • 2023-04-08
        相关资源
        最近更新 更多