在C#中的循环中使用Lambda表达式启动任务

时间:2019-07-02 06:37:15

标签: c# multithreading asynchronous lambda task

在大学准备C#考试时,我发现了以下多项选择题:

  

客户端应用程序通过传递一组操作来调用您的库   去表演。您的图书馆必须确保系统资源最多   有效使用。可以按任何顺序安排作业,但是您的   库必须记录每个操作的位置。您已经声明了   代码:

public IEnumerable<Task> Execute(Action[] jobs)
{
  var tasks = new Task[jobs.Length];

  for (var i = 0; i < jobs.Length; i++)
  {
      /* COMPLETION NEEDED */
  }

  return tasks;
}

public void RunJob(Action job, int index)
{
  // implementation omitted
}
     

通过在f​​or循环中插入代码来完成该方法。选择   正确答案。

1.)
tasks[i] = new Task((idx) => RunJob(jobs[(int)idx], (int)idx), i);
tasks[i].Start();

2.)
tasks[i] = new Task(() => RunJob(jobs[i], i));
tasks[i].Start();

3.)
tasks[i] = Task.Run(() => RunJob(jobs[i], i));

我选择了答案3,因为Task.Run()将指定的工作在线程池中排队,并返回代表该工作的Task对象。

但是使用Task(Action, Object)构造函数,正确答案是1。说明如下:

  

在答案1中,构造函数的第二个参数作为   动作委托的唯一参数。当前值   将值装箱并传递给Task时,将捕获i变量   构造函数。

     

答案2和3使用可捕获i变量的lambda表达式   从封闭方法。 Lambda表达式可能会返回   i的最终值,在这种情况下为10,在操作系统之前   抢占当前线程并开始由创建的每个任务委托   循环。无法确定确切值,因为操作系统   根据您外部的许多因素安排线程执行   程序。

虽然我完全理解答案1的解释,但我对答案2和3的解释却不明白。为什么lambda表达式会返回最终值?

1 个答案:

答案 0 :(得分:1)

在选项2和3中,lambda捕获i循环中使用的原始for变量。不能保证何时在线程池上运行任务。可能的行为是:for循环完成,i=10,然后开始执行任务。因此他们所有人都将使用i=10

类似的行为,您可以在这里看到:

void Do()
{
    var actions = new List<Action>();
    for (int i = 0; i < 3; i++)
    {
        actions.Add(() => Console.WriteLine(i));
    }

    //actions executed after loop is finished
    foreach(var a in actions)
    {
        a();
    }
}

输出为:

3
3
3

您可以这样解决它:

for (int i = 0; i < 3; i++)
{
    var local = i;
    actions.Add(() => Console.WriteLine(local));
}