【问题标题】:Task Parallel Library and Loops任务并行库和循环
【发布时间】:2011-10-25 18:53:10
【问题描述】:

我遇到过这样一种情况,即我正在创建的任务似乎只在我调试代码时才起作用。

正如您将在下面看到的,我不断收到一个超出范围异常的索引,这是不可能的,因为循环永远不会达到 5。

如果我在循环中添加一个断点,然后一直按 F10 直到程序结束,一切都会按预期工作。

任何想法我做错了什么?

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
class Program
{        
    static void Main(string[] args)
    {
        int[] numbers = new int[5] { 1, 2, 3, 4, 5 };
        List<int> nums = new List<int>();

        var tasks = new Task[5];

        for (int i = 0; i < numbers.Length; i++)
        {
            tasks[i] = Task.Factory.StartNew(() =>
                {
                    nums.Add(numbers[i]);
                }, 
                TaskCreationOptions.None);
        }
        Task.WaitAll(tasks);

        for (int i = 0; i < nums.Count; i++)
        {
            Console.WriteLine(nums[i]);
        }
        Console.ReadLine();
    }
}

}

我希望看到 1、2、3、4、5 但不是以任何特定顺序运行异步

奇怪的是,这确实有效,但除了额外的输入之外,我看不出有什么不同。

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
class Program
{        
    static void Main(string[] args)
    {
        int[] numbers = new int[5] { 1, 2, 3, 4, 5 };
        List<int> nums = new List<int>();

        var tasks = new Task[5]
        {
            Task.Factory.StartNew(() => {nums.Add(numbers[0]);}, TaskCreationOptions.None),
            Task.Factory.StartNew(() => {nums.Add(numbers[1]);}, TaskCreationOptions.None),
            Task.Factory.StartNew(() => {nums.Add(numbers[2]);}, TaskCreationOptions.None),
            Task.Factory.StartNew(() => {nums.Add(numbers[3]);}, TaskCreationOptions.None),
            Task.Factory.StartNew(() => {nums.Add(numbers[4]);}, TaskCreationOptions.None)
        };

        Task.WaitAll(tasks);

        for (int i = 0; i < nums.Count; i++)
        {
            Console.WriteLine(nums[i]);
        }
        Console.ReadLine();
    }
}

}

谢谢

迈克

【问题讨论】:

  • 为了清楚起见,您是说第一段代码不起作用,但第二段起作用,对吗?跨度>

标签: c#-4.0 task-parallel-library


【解决方案1】:

由于任务发生在循环之外,在任务执行时已经完成,此时的i为5(循环退出条件)。

在您的第二个示例中,您不使用 i,而是对值进行硬编码,这样问题就消失了。

在调试器中,时间不同,任务可以在循环完成之前执行:再次,问题消失了。

我认为你可以这样修复它:

var ii=i;
tasks[i] = Task.Factory.StartNew(() =>
                {
                    nums.Add(numbers[ii]);
                }, 
                TaskCreationOptions.None);

【讨论】:

【解决方案2】:

更新:在 C#5 中,这不再是问题。 breakingchangewas 使得 foreach 的循环变量在逻辑上位于循环内部。

这是您在 C# 代码中可能犯的最常见错误之一的另一个示例:在匿名委托中捕获循环变量。 Eric Lippert 有一个good explanation 确切的问题所在。

实际上,您的情况更糟,因为理论上它可能随机对错。假设您进行了以下更改:

for (int i = 0; i < numbers.Length; i++)
{
    tasks[i] = ...;
    tasks[i].Wait();
}

然后您的程序突然按预期工作。原因是,您的任务在任务执行时采用i(循环变量)的值,而不是在创建该任务时采用i 的值。因此,在您的原始程序中可能会出现以下顺序:

i = 0
Create task 0
i = 1
Run task 0: This task sees i = 1
Create task 1
i = 2
Create task 2
i = 3
Create task 3
i = 4
Create task 4
i = 5
Run task 1: This task sees i = 5 and throws an exception

Tom 的回答通过在循环中引入一个新变量 ii 解决了这个问题。当每个任务被创建时,它会捕获这个变量,这个变量对于每次迭代都有一个固定的值。

【讨论】:

    【解决方案3】:

    这里的问题是任务访问 i 变量,当任务运行时,这个变量将被改变,而你在最后一次迭代中得到异常的原因是变量 i 将是 5,并且虽然循环会在这个增量之后停止,但是任务体仍然引用它,所以这行会抛出

    nums.Add(numbers[i]);
    

    因为显然 5 超出了范围值。

    要解决此问题,您需要将 i 作为状态参数传递给 StartNew 方法

     tasks[i] = Task.Factory.StartNew((obj) =>
                {
                    int index = (int) obj;
                    nums.Add(numbers[index]);
                }, 
                i);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-22
      • 1970-01-01
      相关资源
      最近更新 更多