запуск задач с лямбда-выражениями в циклах в C # - PullRequest
0 голосов
/ 02 июля 2019

При подготовке к экзамену по 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
}

Завершите метод, вставив код в цикл for.Выберите правильный ответ.

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, представляющий работу.

Но правильный ответ был 1, используя конструктор Task (Action, Object) .В объяснении говорится следующее:

В ответе 1 второй аргумент конструктору передается как единственный аргумент делегату Action.Текущее значение переменной i фиксируется, когда значение упаковывается в коробку и передается в конструктор Task.

В ответах 2 и 3 используется лямбда-выражение, которое захватывает переменную i из включающего метода.Лямбда-выражение, вероятно, вернет окончательное значение i, в данном случае 10, прежде чем операционная система вытеснит текущий поток и начнет каждый делегат задачи, созданный циклом.Точное значение не может быть определено, потому что ОС планирует выполнение потока на основе многих факторов, внешних по отношению к вашей программе.

Хотя я прекрасно понимаю объяснение ответа 1, я не понимаю смысла в объясненияхдля ответов 2 и 3. Почему лямбда-выражение возвращает окончательное значение?

1 Ответ

1 голос
/ 02 июля 2019

В опциях 2 и 3 лямбда захватывает оригинальную переменную 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));
}
...