**

[Конспект Многопоточность](https://github.com/kolei/OAP/blob/master/articles/t5_thread_async.md#задачи-и-класс-task)

** **

Введение в многопоточность. Класс Thread

** Класс Thread в C# позволяет создавать и управлять потоками выполнения в приложении. Это позволяет выполнять различные задачи одновременно, так же оптимизирует программу. **

Параллельное программирование и библиотека TPL

** Данная библиотека позволяет распараллелить задачи и выполнять их сразу на нескольких процессорах, если на целевом компьютере имеется несколько ядер. Кроме того, упрощается сама работа по созданию новых потоков. **

Kласс Task

** Данный класс описывает отдельную задачу, которая запускается асинхронно в одном из потоков из пула потоков. Хотя ее также можно запускать синхронно в текущем потоке. **

Первый способ

** ```angular2html Task task = new Task( () => Console.WriteLine("Hello Task!") ); task.Start(); ``` Он нам ничего не выведет, так как программа завершается быстрее, чем запуск потока. ```angular2html pa.detach.10176 C:/Users/шоумен/RiderProjects/ConsoleApp7/ConsoleApp7/bin/Debug/net8.0/ConsoleApp7.exe Process finished with exit code 0. ``` **

Второй способ

** Этот метот сразу же запускает задачу. ```angular2html Task task = Task.Factory.StartNew( () => Console.WriteLine("Hello Task!") ); ``` В качестве результата метод возвращает запущенную задачу. **

Третий способ

** ```angular2html Task task = Task.Run( () => Console.WriteLine("Hello Task!") ); ``` Выполняет действие и возвращает объект Task. **

Пример 1

** Важно понимать, что задачи не выполняются последовательно.Первая запущенная задача может завершить свое выполнение после последней задачи. ```angular2html using System; using System.Threading.Tasks; Task task1 = new Task( () => Console.WriteLine("Task1 is executed")); task1.Start(); Task task2 = Task.Factory.StartNew( () => Console.WriteLine("Task2 is executed")); Task task3 = Task.Run( () => Console.WriteLine("Task3 is executed")); Console.ReadLine(); ``` Вывод ```angular2html Task1 is executed Task2 is executed Task3 is executed ``` **

Пример 2

** ```angular2html using System; using System.Threading; Task task = new Task(Display); task.Start(); Console.WriteLine( "Завершение метода Main"); Console.ReadLine(); static void Display() { Console.WriteLine( "Начало работы метода Display"); Console.WriteLine( "Завершение работы метода Display"); } ``` Вывод ```angular2html Завершение метода Main Начало работы метода Display Завершение работы метода Display ``` **

Метод Wait

** Чтобы указать, что метод Main должен подождать до конца выполнения задачи, нам надо использовать метод Wait: ```angular2html Task task = new Task(Display); task.Start(); task.Wait(); Console.WriteLine("Завершение метода Main"); Console.ReadLine(); ``` **

Свойства класса Task

** * AsyncState: возвращает объект состояния задачи * CurrentId: возвращает идентификатор текущей задачи * Exception: возвращает объект исключения, возникшего при выполнении задачи * Status: возвращает статус задачи **

Aсинхронное программирование. Асинхронные методы, async и await

** Асинхронность позволяет вынести отдельные задачи из основного потока в специальные асинхронные методы или блоки кода. Ключевыми для работы с асинхронными вызовами в C# являются два ключевых слова: async и await, цель которых - упростить написание асинхронного кода. Они используются вместе для создания асинхронного метода. Асинхонный метод обладает следующими признаками: * В заголовке метода используется модификатор async * Метод содержит одно или несколько выражений await * В качестве возвращаемого типа используется один из следующих: 1. void 2. Task 3. Task 4. ValueTask **

Пример асинхронного метода:

** ```angular2html using System; using System.Threading; using System.Threading.Tasks; // вызов асинхронного метода FactorialAsync(); Console.WriteLine( "Введите число: "); int n = Int32.Parse(Console.ReadLine()); Console.WriteLine( $"Квадрат числа равен {n * n}"); Console.Read(); static void Factorial() { int result = 1; for(int i = 1; i <= 6; i++) { result *= i; } Thread.Sleep(8000); Console.WriteLine( $"Факториал равен {result}"); } // определение асинхронного метода static async void FactorialAsync() { // выполняется синхронно Console.WriteLine( "Начало метода FactorialAsync"); // выполняется асинхронно await Task.Run(()=>Factorial()); Console.WriteLine( "Конец метода FactorialAsync"); } ``` Вывод 1 ```angular2html Введите число: 3 Квадрат числа равен 9 ``` Вывод 2 ```angular2html Введите число: 7 Квадрат числа равен 49 Факториал равен 720 Конец метода FactorialAsync ``` Разберем поэтапно, что здесь происходит: * Запускается метод Main, в котором вызывается асинхронный метод FactorialAsync. * Метод FactorialAsync начинает выполняться синхронно вплоть до выражения await. * Выражение await запускает асинхронную задачу Task.Run(()=>Factorial()) * Пока выполняется асинхронная задача Task.Run(()=>Factorial()) (а она может выполняться довольно продожительное время), выполнение кода возвращается в вызывающий метод - то есть в метод Main. В методе Main нам будет предложено ввести число для вычисления квадрата числа. * В этом и преимущество асинхронных методов - асинхронная задача, которая может выполняться довольно долгое время, не блокирует метод Main, и мы можем продолжать работу с ним, например, вводить и обрабатывать данные. * Когда асинхронная задача завершила свое выполнение (в случае выше - подсчитала факториал числа), продолжает работу асинхронный метод FactorialAsync, который вызвал асинхронную задачу. **

Другой пример - чтение-запись файла:

** ```angular2html using System; using System.Threading; using System.Threading.Tasks; using System.IO; ReadWriteAsync(); Console.WriteLine("Некоторая работа"); Console.Read(); static async void ReadWriteAsync() { string s = "Hello world! One step at a time"; // hello.txt - файл, который будет записываться и считываться using (StreamWriter writer = new StreamWriter("hello.txt", false)) { // асинхронная запись в файл await writer.WriteLineAsync(s); } using (StreamReader reader = new StreamReader("hello.txt")) { // асинхронное чтение из файла string result = await reader.ReadToEndAsync(); Console.WriteLine(result); } } ``` Вывод ```angular2html Некоторая работа Hello world! One step at a time ``` **

Определение асинхронной операции

** Mожем определить асинхронную операцию, используя метод Task.Run(): ```angular2html static void Factorial() { int result = 1; for (int i = 1; i <= 6; i++) { result *= i; } Thread.Sleep(8000); Console.WriteLine($"Факториал равен {result}"); } // определение асинхронного метода static async void FactorialAsync() { // вызов асинхронной операции await Task.Run(()=>Factorial()); } ``` Можно определить асинхронную операцию с помощью лямбда-выражения: ```angular2html static async void FactorialAsync() { await Task.Run(() => { int result = 1; for (int i = 1; i <= 6; i++) { result *= i; } Thread.Sleep(8000); Console.WriteLine($"Факториал равен {result}"); }); } ``` **

Передача параметров в асинхронную операцию

** Выше вычислялся факториал 6, но, допустим, мы хотим вычислять факториалы разных чисел: ```angular2html using System; using System.Threading; using System.Threading.Tasks; FactorialAsync(5); FactorialAsync(6); Console.WriteLine("Некоторая работа"); Console.Read(); static void Factorial(int n) { int result = 1; for (int i = 1; i <= n; i++) { result *= i; } Thread.Sleep(5000); Console.WriteLine($"Факториал равен {result}"); } // определение асинхронного метода static async void FactorialAsync(int n) { await Task.Run(()=>Factorial(n)); } ``` **

Получение результата из асинхронной операции

** Асинхронная операция может возвращать некоторый результат, получить который мы можем так же, как и при вызове обычного метода: ```angular2html using System; using System.Threading; using System.Threading.Tasks; FactorialAsync(5); FactorialAsync(6); Console.Read(); static int Factorial(int n) { int result = 1; for (int i = 1; i <= n; i++) { result *= i; } return result; } // определение асинхронного метода static async void FactorialAsync(int n) { int x = await Task.Run(()=>Factorial(n)); Console.WriteLine($"Факториал равен {x}"); } ``` **

Возвращение результата из асинхронного метода

** В качестве возвращаемого типа в асинхронном методе должны использоваться типы `void` , `Task`, `Task` или `ValueTask` **void** При использовании ключевого слова void асинхронный метод ничего не возвращает: ```angular2html static void Factorial(int n) { int result = 1; for (int i = 1; i <= n; i++) { result *= i; } Console.WriteLine($"Факториал равен {result}"); } // определение асинхронного метода static async void FactorialAsync(int n) { await Task.Run(()=>Factorial(n)); } ``` **Task** Возвращение объекта типа `Task`: ```angular2html using System; using System.Threading.Tasks; FactorialAsync(5); FactorialAsync(6); Console.WriteLine("Некоторая работа"); Console.Read(); static void Factorial(int n) { int result = 1; for (int i = 1; i <= n; i++) { result *= i; } Console.WriteLine($"Факториал равен {result}"); } // определение асинхронного метода static async Task FactorialAsync(int n) { await Task.Run(()=>Factorial(n)); } ``` **Task** Метод может возвращать некоторое значение. Тогда возвращаемое значение оборачивается в объект Task, а возвращаемым типом является `Task`: ```angular2html using System; using System.Threading.Tasks; int n1 = await FactorialAsync(5); int n2 = await FactorialAsync(6); Console.WriteLine($"n1={n1} n2={n2}"); Console.Read(); static int Factorial(int n) { int result = 1; for (int i = 1; i <= n; i++) { result *= i; } return result; } // определение асинхронного метода static async Task FactorialAsync(int n) { return await Task.Run(()=>Factorial(n)); } ``` **ValueTask** Использование типа `ValueTask` во многом аналогично применению `Task` за исключением некоторых различий в работе с памятью, поскольку `ValueTask` - структура, а `Task` - класс. По умолчанию тип `ValueTask` недоступен, и чтобы использовать его, вначале надо установить через NuGet пакет System.Threading.Tasks.Extensions. **

Последовательный и параллельный вызов асинхронных операций

** ```angular2html using System; using System.Threading.Tasks; FactorialAsync(); Console.Read(); static void Factorial(int n) { int result = 1; for (int i = 1; i <= n; i++) { result *= i; } Console.WriteLine($"Факториал числа {n} равен {result}"); } // определение асинхронного метода static async void FactorialAsync() { await Task.Run(() => Factorial(4)); await Task.Run(() => Factorial(3)); await Task.Run(() => Factorial(5)); } ``` Вывод ```angular2html Факториал числа 4 равен 24 Факториал числа 3 равен 6 Факториал числа 5 равен 120 ``` То есть мы видим, что факториалы вычисляются последовательно. И в данном случае вывод строго детерминирован. Нередко такая последовательность бывает необходима, если одна задача зависит от результатов другой. Например, изменим метод FactorialAsync: ```angular2html static async void FactorialAsync() { Task t1 = Task.Run(() => Factorial(4)); Task t2 = Task.Run(() => Factorial(3)); Task t3 = Task.Run(() => Factorial(5)); await Task.WhenAll(new[] { t1, t2, t3 }); } ``` Вывод будет следующим ```angular2html Факториал числа 5 равен 120 Факториал числа 4 равен 24 Факториал числа 3 равен 6 ``` **

Обработка ошибок в асинхронных методах

** ```angular2html using System; using System.Threading; using System.Threading.Tasks; FactorialAsync(-4); FactorialAsync(6); Console.Read(); static void Factorial(int n) { if (n < 1) throw new Exception($"{n} : число не должно быть меньше 1"); int result = 1; for (int i = 1; i <= n; i++) { result *= i; } Console.WriteLine($"Факториал числа {n} равен {result}"); } static async void FactorialAsync(int n) { try { await Task.Run(() => Factorial(n)); } catch (Exception ex) { Console.WriteLine(ex.Message); } } ``` Вывод ```angular2html Факториал числа 6 равен 720 -4 : число не должно быть меньше 1 ``` **

Исследование исключения

** ```angular2html static async void FactorialAsync(int n) { Task task = null; try { task = Task.Run(()=>Factorial(n)); await task; } catch (Exception ex) { Console.WriteLine(task.Exception.InnerException.Message); Console.WriteLine($"IsFaulted: {task.IsFaulted}"); } } ``` И если мы передадим в метод число -1, то task.IsFaulted будет равно true. **

Обработка нескольких исключений. WhenAll

** ```angular2html static async Task DoMultipleAsync() { Task allTasks = null; try { Task t1 = Task.Run(()=>Factorial(-3)); Task t2 = Task.Run(() => Factorial(-5)); Task t3 = Task.Run(() => Factorial(-10)); allTasks = Task.WhenAll(t1, t2, t3); await allTasks; } catch (Exception ex) { Console.WriteLine("Исключение: " + ex.Message); Console.WriteLine("IsFaulted: " + allTasks.IsFaulted); foreach (var inx in allTasks.Exception.InnerExceptions) { Console.WriteLine("Внутреннее исключение: " + inx.Message); } } } ``` Здесь в три вызова метода факториала передаются заведомо некорректные числа: -3, -5, -10. Таким образом, при всех трх вызовах будет сгенерирована ошибка. Хотя блок catch через переменную Exception ex будет получать одно перехваченное исключение, но с помощью коллекции Exception.InnerExceptions мы сможем получить инфрмацию обо всех возникших исключениях. В итоге при выполнении этого метода мы получим следующий консольный вывод: ```angular2html Исключение: -3 : число не должно быть меньше 1 IsFaulted: True Внутреннее исключение: -3: число не должно быть меньше 1 Внутреннее исключение: -5: число не должно быть меньше 1 Внутреннее исключение: -10: число не должно быть меньше 1 ``` **

await в блоках catch и finally

** ```angular2html static async void FactorialAsync(int n) { try { await Task.Run(() => Factorial(n)); ; } catch (Exception ex) { await Task.Run(()=>Console.WriteLine(ex.Message)); } finally { await Task.Run(() => Console.WriteLine("await в блоке finally")); } } ``` **

Отмена асинхронных операций

** Для отмены асинхронных операций используются классы CancellationToken и CancellationTokenSource. CancellationToken содержит информацию о том, надо ли отменять асинхронную задачу. Асинхронная задача, в которую передается объект CancellationToken, периодически проверяет состояние этого объекта. Если его свойство IsCancellationRequested равно true, то задача должна остановить все свои операции. Для создания объекта CancellationToken применяется объект CancellationTokenSource. Кроме того, при вызове у CancellationTokenSource метода Cancel() у объекта CancellationToken свойство IsCancellationRequested будет установлено в true. Рассмотрим применение этих классов на примере: ```angular2html using System; using System.Threading; using System.Threading.Tasks; CancellationTokenSource cts = new CancellationTokenSource(); CancellationToken token = cts.Token; FactorialAsync(6, token); Thread.Sleep(3000); cts.Cancel(); Console.Read(); static void Factorial(int n, CancellationToken token) { int result = 1; for (int i = 1; i <= n; i++) { if (token.IsCancellationRequested) { Console.WriteLine( "Операция прервана токеном"); return; } result *= i; Console.WriteLine( $"Факториал числа {i} равен {result}"); Thread.Sleep(1000); } } // определение асинхронного метода static async void FactorialAsync(int n, CancellationToken token) { if(token.IsCancellationRequested) return; await Task.Run(()=>Factorial(n, token)); } ``` Вывод ```angular2html Факториал числа 1 равен 1 Факториал числа 2 равен 2 Факториал числа 3 равен 6 Операция была отменена. ```