Task в циклах for

Добрый вечер, пытаюсь с помощью Task создать несколько процессов и запустить функцию в каждом из них, но всегда получается одинаковая итерация. Вот пример кода:
var Task_1 = Task.Factory.StartNew(() =>
{
for (var i = 0; i < 7; i++)
{
Task.Factory.StartNew(() =>
{
Console.WriteLine(i);
//Task.Factory.StartNew(matrix.Multiplication, i, TaskCreationOptions.AttachedToParent);
}, TaskCreationOptions.AttachedToParent);
}
});
Task.WaitAll(Task_1);
На экране не выводится 7 различных цифр в какой-либо последовательности, при последней компиляции вывелась одна четверка и все остальные семерки. Объясните кто-нибудь в чем проблема и как это обойти? Я пробовал в цикле ожидать завершении каждого процесса (пример: task.Wait()), но тогда уже ведь задача не является параллельной.
задан 7 дек ’15 в 18:20
ed8009ed8009
55955 серебряных знаков1919 бронзовых знаков
Проблема в том, что i — переменная! А значит, её значение меняется. Когда задача начинает выполняться, значение i уже вполне может измениться. А вы ведь выводите всё время одну и ту же переменную i.
Я бы написал так:
Task Output(int i)
{
return Task.Run(() => Console.WriteLine(i));
}
var tasks = new List<Task>();
for (int i = 0; i < 7; i++)
tasks.Add(Output(i));
await Task.WhenAll(tasks);
При этом значение i копируется в момент вызова функции.
ответ дан 7 дек ’15 в 18:25
VladDVladD
194k2424 золотых знака256256 серебряных знаков481481 бронзовый знак
В свое время сталкивался с этой проблемой. Объяснение нашел на английском SO. К сожалению не могу найти ссылку. Вынесите переменную в локальную итерацию, примерно так:
var th = Task.Factory.StartNew(() =>
{
for (var i = 0; i < 7; i++)
{
int localVar = i;
Task.Factory.StartNew(() =>
{
Console.WriteLine(localVar);
}, TaskCreationOptions.AttachedToParent);
}
});
ответ дан 7 дек ’15 в 18:24
AlexisAlexis
3,43522 золотых знака2727 серебряных знаков5454 бронзовых знака
Спасибо всем, вроде разобрался, решил проблему следующим образом:
var Task_1 = Task.Factory.StartNew(() =>
{
Task.Factory.StartNew(() =>
{
for (int j = 0; j <= Environment.ProcessorCount – 1; j++)
{
Console.WriteLine(j);
//Task.Factory.StartNew(matrix.Multiplication, j, TaskCreationOptions.AttachedToParent);
}
}, TaskCreationOptions.AttachedToParent);
});
Task.WaitAll(Task_1);
ответ дан 7 дек ’15 в 18:29
ed8009ed8009
55955 серебряных знаков1919 бронзовых знаков
Всё ещё ищете ответ? Посмотрите другие вопросы с метками c# task или задайте свой вопрос.
Источник
Циклы
Последнее обновление: 19.06.2017
Циклы являются управляющими конструкциями, позволяя в зависимости от определенных условий выполнять некоторое действие множество раз. В
C# имеются следующие виды циклов:
for
foreach
while
do…while
Цикл for
Цикл for имеет следующее формальное определение:
for ([инициализация счетчика]; [условие]; [изменение счетчика])
{
// действия
}
Рассмотрим стандартный цикл for:
for (int i = 0; i < 9; i++)
{
Console.WriteLine($”Квадрат числа {i} равен {i*i}”);
}
Первая часть объявления цикла – int i = 0 – создает и инициализирует счетчик i. Счетчик необязательно должен представлять тип
int. Это может быть и другой числовой тип, например, float. И перед выполнением цикла его значение будет равно 0. В данном
случае это то же самое, что и объявление переменной.
Вторая часть – условие, при котором будет выполняться цикл. Пока условное выражение возвращает true, будет выполняться цикл.
В данном случае цикл будет выполняться, пока счетчик i не достигнет 9.
И третья часть – приращение счетчика на единицу. Опять же нам необязательно увеличивать на единицу. Можно уменьшать: i–.
В итоге блок цикла сработает 9 раз, пока значение i не станет равным 9. И каждый раз это значение будет увеличиваться на 1.
Нам необязательно указывать все условия при объявлении цикла. Например, мы можем написать так:
int i = 0;
for (; 😉
{
Console.WriteLine($”Квадрат числа {++i} равен {i * i}”);
}
Формально определение цикла осталось тем же, только теперь блоки в определении у нас пустые: for (; i <;). У нас нет
инициализированной переменной-счетчика, нет условия, поэтому цикл будет работать вечно – бесконечный цикл.
Мы также можем опустить ряд блоков:
int i = 0;
for (; i<9;)
{
Console.WriteLine($”Квадрат числа {++i} равен {i * i}”);
}
Этот пример по сути эквивалентен первому примеру: у нас также есть счетчик, только создан он вне цикла. У нас есть условие выполнения цикла.
И есть приращение счетчика уже в самом блоке for.
Цикл do
В цикле do сначала выполняется код цикла, а потом происходит проверка условия в инструкции while. И пока это условие истинно,
цикл повторяется. Например:
int i = 6;
do
{
Console.WriteLine(i);
i–;
}
while (i > 0);
Здесь код цикла сработает 6 раз, пока i не станет равным нулю. Но важно отметить, что цикл do гарантирует хотя бы единократное выполнение действий,
даже если условие в инструкции while не будет истинно. То есть мы можем написать:
int i = -1;
do
{
Console.WriteLine(i);
i–;
}
while (i > 0);
Хотя у нас переменная i меньше 0, цикл все равно один раз выполнится.
Цикл while
В отличие от цикла do цикл while сразу проверяет истинность некоторого условия, и если условие истинно, то код цикла выполняется:
int i = 6;
while (i > 0)
{
Console.WriteLine(i);
i–;
}
Операторы continue и break
Иногда возникает ситуация, когда требуется выйти из цикла, не дожидаясь его завершения. В этом случае мы можем воспользоваться оператором
break.
Например:
for (int i = 0; i < 9; i++)
{
if (i == 5)
break;
Console.WriteLine(i);
}
Хотя в условии цикла сказано, что цикл будет выполняться, пока счетчик i не достигнет значения 9, в реальности цикл сработает 5 раз.
Так как при достижении счетчиком i значения 5, сработает оператор break, и цикл завершится.
Теперь поставим себе другую задачу. А что если мы хотим, чтобы при проверке цикл не завершался, а просто пропускал текущую итерацию.
Для этого мы можем воспользоваться оператором continue:
for (int i = 0; i < 9; i++)
{
if (i == 5)
continue;
Console.WriteLine(i);
}
В этом случае цикл, когда дойдет до числа 5, которое не удовлетворяет условию проверки, просто пропустит это число и перейдет к следующей итерации:
Цикл foreach
Цикл foreach перебирает коллекции, например, массивы, и будет рассмотрен далее в теме массивов.
Источник
Последнее обновление: 18.11.2017
Вложенные задачи
Одна задача может запускать другую – вложенную задачу. При этом эти задачи выполняются независимо друг от друга. Например:
static void Main(string[] args)
{
var outer = Task.Factory.StartNew(() => // внешняя задача
{
Console.WriteLine(“Outer task starting…”);
var inner = Task.Factory.StartNew(() => // вложенная задача
{
Console.WriteLine(“Inner task starting…”);
Thread.Sleep(2000);
Console.WriteLine(“Inner task finished.”);
});
});
outer.Wait(); // ожидаем выполнения внешней задачи
Console.WriteLine(“End of Main”);
Console.ReadLine();
}
Несмотря на то, что здесь мы ожидаем выполнения внешней задачи, но вложенная задача может завершить выполнение
даже после завершения метода Main:
Outer task starting…
End of Main
Inner task starting…
Inner task finished.
Если необходимо, чтобы вложенная задача выполнялась вместе с внешней, необходимо использовать значение TaskCreationOptions.AttachedToParent:
static void Main(string[] args)
{
var outer = Task.Factory.StartNew(() => // внешняя задача
{
Console.WriteLine(“Outer task starting…”);
var inner = Task.Factory.StartNew(() => // вложенная задача
{
Console.WriteLine(“Inner task starting…”);
Thread.Sleep(2000);
Console.WriteLine(“Inner task finished.”);
}, TaskCreationOptions.AttachedToParent);
});
outer.Wait(); // ожидаем выполнения внешней задачи
Console.WriteLine(“End of Main”);
Console.ReadLine();
}
Консольный вывод:
Outer task starting…
Inner task starting…
Inner task finished.
End of Main
Массив задач
Также как и с потоками, мы можем создать и запустить массив задач. Можно определить все задачи в массиве непосредственно через объект Task:
Task[] tasks1 = new Task[3]
{
new Task(() => Console.WriteLine(“First Task”)),
new Task(() => Console.WriteLine(“Second Task”)),
new Task(() => Console.WriteLine(“Third Task”))
};
// запуск задач в массиве
foreach (var t in tasks1)
t.Start();
Либо также можно использовать методы Task.Factory.StartNew или Task.Run и сразу запускать все задачи:
Task[] tasks2 = new Task[3];
int j = 1;
for (int i = 0; i < tasks2.Length; i++)
tasks2[i] = Task.Factory.StartNew(() => Console.WriteLine($”Task {j++}”));
Но в любом случае мы опять же можем столкнуться с тем, что все задачи из массива могут завершиться после того, как отработает метод Main, в котором запускаются эти задачи:
static void Main(string[] args)
{
Task[] tasks1 = new Task[3]
{
new Task(() => Console.WriteLine(“First Task”)),
new Task(() => Console.WriteLine(“Second Task”)),
new Task(() => Console.WriteLine(“Third Task”))
};
foreach (var t in tasks1)
t.Start();
Task[] tasks2 = new Task[3];
int j = 1;
for (int i = 0; i < tasks2.Length; i++)
tasks2[i] = Task.Factory.StartNew(() => Console.WriteLine($”Task {j++}”));
Console.WriteLine(“Завершение метода Main”);
Console.ReadLine();
}
Один из возможных консольных выводов программы:
Second Task
Task 1
Завершение метода Main
Third Task
Task 3
First Task
Task 2
Если необходимо выполнять некоторый код лишь после того, как все задачи из массива завершатся, то применяется метод Task.WaitAll(tasks):
static void Main(string[] args)
{
Task[] tasks1 = new Task[3]
{
new Task(() => Console.WriteLine(“First Task”)),
new Task(() => Console.WriteLine(“Second Task”)),
new Task(() => Console.WriteLine(“Third Task”))
};
foreach (var t in tasks1)
t.Start();
Task.WaitAll(tasks1); // ожидаем завершения задач
Console.WriteLine(“Завершение метода Main”);
Console.ReadLine();
}
В этом случае сначала завершатся все задачи, и лишь только потом будет выполняться последующий код из метода Main:
Second Task
Third Task
First Task
Завершение метода Main
В то же время порядок выполнения самих задач в массиве также недетерминирован.
Также мы можем применять метод Task.WaitAny(tasks). Он ждет, пока завершится хотя бы одна из массива задач.
Возвращение результатов из задач
Задачи могут не только выполняться как процедуры, но и возвращать определенные результаты:
class Program
{
static void Main(string[] args)
{
Task<int> task1 = new Task<int>(()=>Factorial(5));
task1.Start();
Console.WriteLine($”Факториал числа 5 равен {task1.Result}”);
Task<Book> task2 = new Task<Book>(() =>
{
return new Book { Title = “Война и мир”, Author = “Л. Толстой” };
});
task2.Start();
Book b = task2.Result; // ожидаем получение результата
Console.WriteLine($”Название книги: {b.Title}, автор: {b.Author}”);
Console.ReadLine();
}
static int Factorial(int x)
{
int result = 1;
for (int i = 1; i <= x; i++)
{
result *= i;
}
return result;
}
}
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
}
Во-первых, чтобы задать возвращаемый из задачи тип объекта, мы должны типизировать Task. Например, Task<int> – в данном
случае задача будет возвращать объект int.
И, во-вторых, в качестве задачи должен выполняться метод, возвращающий данный тип объекта. Например, в первом случае
у нас в качестве задачи выполняется функция Factorial, которая принимает числовой параметр и также на выходе возвращает число.
Возвращаемое число будет храниться в свойстве Result: task1.Result. Нам не надо его приводить к типу int, оно уже само по себе
будет представлять число.
То же самое и со второй задачей task2. В этом случае в лямбда-выражении возвращается объект Book. И также мы его получаем с помощью
task2.Result
При этом при обращении к свойству Result программа текущий поток останавливает выполнение и ждет, когда будет получен результат из выполняемой задачи.
Источник
Источник
В этой статье мы продолжим наш цикл про написание скриптов на Bash. Мы уже рассмотрели как работать с архивами и создавать функции, но этого еще недостаточно. Любой уважающий себя язык программирования должен содержать циклы. Цикл – это такая последовательность, которая позволяет выполнять определенный участок кода необходимое количество раз.
С помощью циклов вы можете очень сильно сократить количество строк кода, которые необходимо написать для однотипных операций. В этой статье мы рассмотрим что такое циклы Bash, как их создавать и использовать.
Циклы Bash
Как я уже сказал, циклы позволяют выполнять один и тот же участок кода необходимое количество раз. В большинстве языков программирования существует несколько типов циклов. Большинство из них поддерживаются оболочкой Bash. Мы рассмотрим их все в сегодняшней статье, но сначала поговорим какими они бывают:
- for – позволяет перебрать все элементы из массива или использует переменную-счетчик для определения количества повторений;
- while – цикл выполняется пока условие истинно;
- until – цикл выполняется пока условие ложно.
Циклы Bash, это очень полезная вещь и разобраться с ними будет несложно. Bash позволяет использовать циклы как в скриптах, так и непосредственно в командной оболочке. Дальше мы рассмотрим каждый из этих видов циклов.
Цикл for
Цикл for bash применяется очень часто из-за своей специфики. Его проще всего использовать когда вы знаете сколько раз нужно повторить операцию или вам нужно просто обработать по очереди все элементы массива и вы не хотите контролировать количество повторений.
Цикл for имеет несколько синтаксисов и может вести себя по разному. Для перебора элементов списка удобно использовать такой синтаксис:
for переменная in список
do
команда1
команда2
done
Каждый цикл for независимо от типа начинается с ключевого слова for. Дальше все зависит от типа. В этом случае после for указывается имя переменной, в которую будет сохранен каждый элемент списка, затем идет ключевое слово in и сам список. Команды, которые нужно выполнять в цикле размещаются между словами do и done.
Проверим все на практике и напишем небольшой скрипт, который будет выводить номера от 1 до 5:
vi for1.sh
!/bin/bash
for number in 1 2 3 4 5
do
echo $number
done
Дайте скрипту права на выполнение и запустите его:
chmod +x for1.sh
./for1.sh
Вы увидите, что все выполняется так, как и предполагалось. Программа выведет цифры от 1 до 5, которые мы перечислили в массиве. Вы можете передать циклу любой массив, например, вывод какой-либо другой команды:
!/bin/bash
for iface in $(ls /sys/class/net/)
do
echo $iface
done
Как вы уже поняли, этот цикл выводит список всех, подключенных к системе сетевых интерфейсов. Но в цикле вы можете не только их выводить, но и выполнять какие-либо действия.
Следующий тип цикла for похож на его реализацию в языках программирования Си и С++. Он состоит из трех частей, инициализатора счетчика, условия продолжения выполнения и действия над счетчиком. Вот синтаксис:
for ((счетчик=1; счетчик < 10; счетчик++))
do
команда1
команда2
done
Этот цикл немного сложнее, но он позволяет сделать больше. С помощью такого цикла можно не только перебирать массивы, но и сделать необходимое количество повторений. Рассмотрим пример:
!/bin/bash
for ((i=1; i < 10; i++))
do
echo $i
done
Результат странный, правда? Обратите внимание, как выполняется проверка условия. Значение счетчика сравнивается с эталоном. Действие с переменной счетчиком выполняется сразу же после завершения основного блока команд, а уже потом делается сравнение. Таким образом, у нас при первом выполнении i равно 1, а после него уже два 2. Теперь к завершению девятого прохода значение счетчика будет увеличено на единицу и станет 10. Условие сравнения 10 < 10 не верно, поэтому цикл дальше не выполняется.
С помощью этого же синтаксиса можно сделать бесконечные циклы bash linux:
!/bin/bash
for ((;;))
do
echo “Бесконечный цикл, нажмите CTRL+C для выхода”
done
Если необходимо, вы можете выйти из цикла преждевременно. Но так делать не рекомендуется. Для выхода используйте команду break:
!/bin/bash
for (i=1;i<10;i++)
do
echo Значение счетчика $i
if [[ i -gt 5]]
break
fi
done
Со циклами for мы завершили, теперь вы знаете как они выглядят и что с ними делать. Дальше мы рассмотрим циклы while и until. Они намного проще и более универсальны.
Цикл While
Суть цикла While в том, что он будет повторяться до тех пор, пока будет выполняться условие, указанное в объявлении цикла. Здесь нет никаких счетчиков и если они вам нужны, то организовывать их вам придется самим. Bash цикл while имеет такой синтаксис:
while [ условие ]
do
команда1
команда2
команда3
done
Это работает так: сначала выполняется проверка на правильность условия, если true, выполняется набор команд, затем снова выполняется проверка, и так пока условие не вернет отрицательный результат. Это нам тоже нужно сделать вручную. Рассмотрим пример:
vi while.sh
!/bin/bash
x=1
while [ $x -lt 5 ]
do
echo “Значение счетчика: $x”
x=$(( $x + 1 ))
done
Здесь сначала мы устанавливаем значение счетчика в единицу, затем, в условии сравниваем его с 5, пока счетчик меньше пяти будут выполняться команды из блока do-done. Но еще в этом блоке нам нужно увеличивать значение счетчика на единицу, что мы и делаем.
Также с помощью while мы можем прочитать несколько строк из стандартного ввода:
vi while.sh
!/bin/bash
hile read line
do
echo $line
done
Программа будет запрашивать новые строки пока вы не передадите ей символ конца файла с помощью сочетания клавиш Ctrl+D. Бесконечный цикл while bash с помощью while еще проще организовать:
vi while.sh
while :
do
echo “Бесконечный цикл bash, для выхода нажмите Ctrl+C”
done
Цикл until
Нам осталось рассмотреть последний цикл. Это цикл until. Он очень похож на while и отличается от него только работой условия. Если в первом нужно чтобы условие всегда было истинным, то здесь все наоборот. Цикл будет выполняться пока условие неверно. Синтаксис:
until [ условие ]
do
команда1
команда2
done
Думаю после рассмотрения реального примера со счетчиком будет намного понятнее как это работает:
!/bin/bash
count=1
until [ $count -gt 10 ]
do
echo “Значение счетчика: $count”
count=$(( $count + 1 ))
done
Мы задаем значение счетчика 1 и увеличиваем его на единицу при каждом проходе. Как только условие счетчик больше 10 выполнится, сразу цикл будет остановлен. В циклах while и until тоже можно использовать команды break и continue для выхода из цикла и завершения текущего прохода, но мы не будем это очень подробно рассматривать.
Выводы
В этой статье мы рассмотрели циклы Bash, как с ними работать и в каких ситуациях их можно использовать. Циклы могут стать отличным и незаменимым инструментом при создании сложных скриптов администрирования системы. Если у вас остались вопросы, спрашивайте в комментариях!
Источник