Одна задача может запускать другую – вложенную задачу. При этом эти задачи выполняются независимо друг от друга.
Несмотря на то, что здесь мы ожидаем выполнения внешней задачи, но вложенная задача может завершить выполнение даже после завершения метода Main:
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");Outer task starting...
End of MainПри этом внутренняя задача может даже не начать свое выполнение к завершению работы основного потока программы. То есть в данном случае внешняя и вложенная задачи выполняются независимо друг от друга.
Если необходимо, чтобы вложенная задача выполнялась как часть внешней, необходимо использовать значение TaskCreationOptions.AttachedToParent: В данном случае вложенная задача прикреплена к внешней и выполняется как часть внешней задачи. И внешняя задача завершится только когда завершатся все прикрепленные к ней вложенные задачи.
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");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 и сразу запускать все задачи:
sk[] 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, в котором запускаются эти задачи:
Если необходимо выполнять некоторый код лишь после того, как все задачи из массива завершатся, то применяется метод Task.WaitAll(tasks):
В то же время порядок выполнения самих задач в массиве также недетерминирован.
Task[] tasks = new Task[3];
for(var i = 0; i < tasks.Length; i++)
{
tasks[i] = new Task(() =>
{
Thread.Sleep(1000); // эмуляция долгой работы
Console.WriteLine($"Task{i} finished");
});
tasks[i].Start(); // запускаем задачу
}
Console.WriteLine("Завершение метода Main");
Task.WaitAll(tasks); // ожидаем завершения всех задач
Возвращение результатов
int n1 =4, n2 = 5;
Task<int> sumTask = new Task<int>(() => Sum(n1, n2));
sumTask.Start();
int result = sumTask.Result;
Console.WriteLine($"{n1} + {n2} = {result}"); // 4 + 5 = 9
int Sum(int a, int b) => a + b;Чтобы получать из задачи результат, необходимо типизировать объект Task тем типом, объект которого мы хотим получить из задачи. Например, в примере выше мы ожидаем из задачи sumTask получить число типа int, соответственно типизируем объект Task данным типом – Task<int>.
И, во-вторых, в качестве задачи должен выполняться метод, который возвращает данный тип объекта. Так, в данном случае у нас в качестве задачи выполняется метод Sum, которая принимаетдва числа и на выходе возвращает их сумму – значение типа int.
Возвращаемое число будет храниться в свойстве Result: sumTask.Result. Нам не надо его приводить к типу int, оно уже само по себе будет представлять число.
При обращении к свойству Result текущий поток останавливает выполнение и ждет, когда будет получен результат из выполняемой задачи.
Другой пример
Task<Person> defaultPersonTask = new Task<Person>(() => new Person("Tom", 37));
defaultPersonTask.Start();
Person defaultPerson = defaultPersonTask.Result;
Console.WriteLine($"{defaultPerson.Name} - {defaultPerson.Age}"); // Tom - 37
record class Person(string Name, int Age);