Предыдущая лекция | &nbsp; | Следующая лекция
:----------------:|:----------:|:----------------:
[Исключения. Null.](./t5_exception.md) | [Содержание](../readme.md#тема-5-продвинутый-c-функции-лямбды-исключения-работа-с-файлами-многопоточность-регулярные-выражения) | [Работа с файловой системой и файлами.](./t5_files.md)

# Многопоточность

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

Одним из ключевых аспектов в современном программировании является **многопоточность**. Ключевым понятием при работе с многопоточностью является **поток**. **Поток** предствляет некоторую часть кода программы. При выполнении программы каждому потоку выделяется определенный квант времени. И при помощи многопоточности мы можем выделить в приложении несколько потоков, которые будут выполнять различные задачи одновременно. Если у нас, допустим, графическое приложение, которое посылает запрос к какому-нибудь серверу или считывает и обрабатывает огромный файл, то без многопоточности у нас бы блокировался графический интерфейс на время выполнения задачи. А благодаря потокам мы можем выделить отправку запроса или любую другую задачу, которая может долго обрабатываться, в отдельный поток. Поэтому, к примеру, клиент-серверные приложения (и не только они) практически не мыслимы без многопоточности.

Основной функционал для использования потоков в приложении сосредоточен в пространстве имен `System.Threading`. В нем определен класс, представляющий отдельный поток - класс **Thread**.

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

В эпоху многоядерных машин, которые позволяют параллельно выполнять сразу несколько процессов, стандартных средств работы с потоками в .NET уже оказалось недостаточно. Поэтому во фреймворк .NET была добавлена библиотека параллельных задач TPL (Task Parallel Library), основной функционал которой располагается в пространстве имен `System.Threading.Tasks`. Данная библиотека позволяет распараллелить задачи и выполнять их сразу на нескольких процессорах, если на целевом компьютере имеется несколько ядер. Кроме того, упрощается сама работа по созданию новых потоков. Поэтому начиная с .NET 4.0. рекомендуется использовать именно TPL и ее классы для создания многопоточных приложений, хотя стандартные средства и класс **Thread** по-прежнему находят широкое применение.

### Задачи и класс Task

В основе библиотеки TPL лежит концепция задач, каждая из которых описывает отдельную продолжительную операцию. В библиотеке классов .NET задача представлена специальным классом - классом **Task**, который находится в пространстве имен `System.Threading.Tasks`. Данный класс описывает отдельную задачу, которая запускается асинхронно в одном из потоков из пула потоков. Хотя ее также можно запускать синхронно в текущем потоке.

Для определения и запуска задачи можно использовать различные способы. Первый способ создание объекта **Task** и вызов у него метода *Start*:

```cs
Task task = new Task(
    () => Console.WriteLine("Hello Task!")
);
task.Start();
```

>Если запустить код в таком виде, то строку "Hello Task" мы в консоли не увидим. Дело в том, что процесс запуска потока не моментальный и программа успеет завершиться до запуска потока.

```
/home/kei/RiderProjects/asyncAwait/bin/Debug/net8.0/asyncAwait

Process finished with exit code 0.
```

В качестве параметра объект **Task** принимает делегат **Action**, то есть мы можем передать любое действие, которое соответствует данному делегату, например, лямбда-выражение, как в данном случае, или ссылку на какой-либо метод. То есть в данном случае при выполнении задачи на консоль будет выводиться строка "Hello Task!".

А метод *Start* собственно запускает задачу.

Второй способ заключается в использовании статического метода `Task.Factory.StartNew()`. Этот метод также в качестве параметра принимает делегат **Action**, который указывает, какое действие будет выполняться. При этом этот метод сразу же запускает задачу:

```cs
Task task = Task.Factory.StartNew(
    () => Console.WriteLine("Hello Task!")
);
```

В качестве результата метод возвращает запущенную задачу.

Третий способ определения и запуска задач представляет использование статического метода `Task.Run()`:

```cs
Task task = Task.Run(
    () => Console.WriteLine("Hello Task!")
);
```

Метод `Task.Run()` также в качестве параметра может принимать делегат **Action** - выполняемое действие и возвращает объект **Task**.

Определим небольшую программу, где используем все эти способы:

```cs
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();
```

### Ожидание задачи

Важно понимать, что задачи не выполняются последовательно. Первая запущенная задача может завершить свое выполнение после последней задачи.

Или рассмотрим еще один пример:

```cs
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");
}
```

Класс **Task** в качестве параметра принимает метод *Display*, который соответствует делегату **Action**. Далее чтобы запустить задачу, вызываем метод *Start*: `task.Start()`, и после этого метод *Display* начнет выполняться во вторичном потоке. В конце метода Main выводит некоторый маркер-строку, что метод _Main_ завершился.

Однако в данном случае консольный вывод может выглядеть следующим образом:

```
Завершение метода Main
Начало работы метода Display
Завершение работы метода Display
```

То есть мы видим, что даже когда основной код в методе _Main_ уже отработал, запущенная ранее задача ещё не завершилась.

Чтобы указать, что метод _Main_ должен подождать до конца выполнения задачи, нам надо использовать метод *Wait*:

```cs
Task task = new Task(Display);
task.Start();
task.Wait();
Console.WriteLine("Завершение метода Main");
Console.ReadLine();
```

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

Класс **Task** имеет ряд свойств, с помощью которых мы можем получить информацию об объекте. Некоторые из них:

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

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

Асинхронность позволяет вынести отдельные задачи из основного потока в специальные асинхронные методы или блоки кода. Особенно это актуально в графических программах, где продолжительные задачи могу блокировать интерфейс пользователя. И чтобы этого не произошло, нужно задействовать асинхронность. Также асинхронность несет выгоды в веб-приложениях при обработке запросов от пользователей, при обращении к базам данных или сетевым ресурсам. При больших запросах к базе данных асинхронный метод просто "уснёт" на время, пока не получит данные от БД, а основной поток сможет продолжить свою работу. В синхронном же приложении, если бы код получения данных находился в основном потоке, этот поток просто бы блокировался на время получения данных.

Ключевыми для работы с асинхронными вызовами в C# являются два ключевых слова: **async** и **await**, цель которых - упростить написание асинхронного кода. Они используются вместе для создания асинхронного метода.

Асинхонный метод обладает следующими признаками:

* В заголовке метода используется модификатор **async**
* Метод содержит одно или несколько выражений **await**
* В качестве возвращаемого типа используется один из следующих:

    * `void`
    * `Task`
    * `Task<T>`
    * `ValueTask<T>`

Асинхронный метод, как и обычный, может использовать любое количество параметров или не использовать их вообще. Однако асинхронный метод не может определять параметры с модификаторами **out** и **ref**.

Также стоит отметить, что слово **async**, которое указывается в определении метода, не делает автоматически метод асинхронным. Оно лишь указывает, что данный метод **может** содержать одно или несколько выражений **await**.

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

```cs
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");
}
```

Здесь прежде всего определен обычный метод подсчета факториала. Для имитации долгой работы в нем используется задержка на `8` секунд с помощью метода `Thread.Sleep()`. Условно это некоторый метод, который выполняет некоторую работу продолжительное время. Но для упрощения понимания он просто подсчитывает факториал числа `6`.

Также здесь определен асинхронный метод *FactorialAsync()*. Асинхронным он является потому, что имеет в определении перед возвращаемым типом модификатор **async**, его возвращаемым типом является _void_, и в теле метода определено выражение **await**.

Выражение **await** определяет задачу, которая будет выполняться асинхронно. В данном случае подобная задача представляет выполнение функции факториала:

```cs
await Task.Run(()=>Factorial());
```

>По негласным правилам в названии асинхроннных методов принято использовать суффикс *Async* - *FactorialAsync()*, хотя в принципе это необязательно делать.

И в методе _Main_ мы вызываем этот асинхронный метод.

Посмотрим, какой у программы будет консольный вывод:

```
Начало метода FactorialAsync
Введите число: 
7
Квадрат числа равен 49
Конец метода Main
Факториал равен 720
Окончание метода FactorialAsync
```

Разберем поэтапно, что здесь происходит:

* Запускается метод _Main_, в котором вызывается асинхронный метод _FactorialAsync_.
* Метод _FactorialAsync_ начинает выполняться синхронно вплоть до выражения **await**.
* Выражение **await** запускает асинхронную задачу `Task.Run(()=>Factorial())`
* Пока выполняется асинхронная задача `Task.Run(()=>Factorial())` (а она может выполняться довольно продожительное время), выполнение кода возвращается в вызывающий метод - то есть в метод _Main_. В методе _Main_ нам будет предложено ввести число для вычисления квадрата числа.

    В этом и преимущество асинхронных методов - асинхронная задача, которая может выполняться довольно долгое время, не блокирует метод _Main_, и мы можем продолжать работу с ним, например, вводить и обрабатывать данные.

* Когда асинхронная задача завершила свое выполнение (в случае выше - подсчитала факториал числа), продолжает работу асинхронный метод _FactorialAsync_, который вызвал асинхронную задачу.

Функция факториала, возможно, представляет не самый показательный пример, так как в реальности в данном случае нет смысла делать ее асинхронной. Но рассмотрим другой пример - чтение-запись файла:

```cs
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);
    }
} 
```

Асинхронный метод *ReadWriteAsync* выполняет запись в файл некоторой строки и затем считывает записанный файл. Подобные операции могут занимать продолжительное время, особенно при больших объемах данных, поэтому такие операции лучше делать асинхронными.

Фреймворк .NET уже имеет встроенную поддержку таких операций. Например, в классе **StreamWriter** определен метод *WriteLineAsync*. По сути он уже представляет асинхронную операцию и принимает в качестве параметра некоторую строку, которую надо записать в файл. Поскольку этот метод представляет асинхронную операцию, то вызов этого метода мы можем оформить в выражение **await**:

```cs
// асинхронная запись в файл
await writer.WriteLineAsync(s);  
```

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

Во фреймворке определено много подобных методов. Как правило, они связаны с работой с файлами, отправкой сетевых запросов или запросов к базе данных. Их легко узнать по суффиксу *Async*. То есть если метод имеет подобный суффикс в названии, то с большей степенью вероятности его можно использовать в выражении **await**.

Далее в методе _Main_ вызывается асинхронный метод *ReadWriteAsync*:

```cs
ReadWriteAsync();
            
Console.WriteLine("Некоторая работа");
Console.Read();
```

И опять же, когда выполнение в методе *ReadWriteAsync* доходит до первого выражения **await**, управление возвращается в метод *Main*, и мы можем продолжать с ним работу. Запись в файл и считывание файла будут производиться параллельно и не будут блокировать работу метода *Main*.

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

Как выше уже было сказано, фреймворк .NET имеет много встроенных методов, которые представляют асинхронную операцию. Они заканчиваются на суффикс *Async*. И перед вызывами подобных методов мы можем указывать оператор **await**. Например:

```cs
StreamWriter writer = new StreamWriter("hello.txt", false);
// асинхронная запись в файл
await writer.WriteLineAsync("Hello");  
```

Либо мы сами можем определить асинхронную операцию, используя метод `Task.Run()`:

```cs
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()); 
}
```

Можно определить асинхронную операцию с помощью лямбда-выражения:

```cs
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`, но, допустим, мы хотим вычислять факториалы разных чисел:

```cs
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));
}
```

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

Асинхронная операция может возвращать некоторый результат, получить который мы можем так же, как и при вызове обычного метода:

```cs
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}");
}
```

Метод *Factorial* возвращает значение типа **int**, это значение мы можем получить, просто присвоив результат асинхронной операции переменной данного типа: `int x = await Task.Run(()=>Factorial(n));`

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

В качестве возвращаемого типа в асинхронном методе должны использоваться типы `void`, `Task`, `Task<T>` или `ValueTask<T>`

#### void

При использовании ключевого слова **void** асинхронный метод ничего не возвращает:

```cs
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**:

```cs
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));
}
```

Формально метод *FactorialAsync* не использует оператор **return** для возвращения результата. Однако если в асинхронном методе выполняется в выражении **await** асинхронная операция, то мы можем возвращать из метода объект **Task**.

#### `Task<T>`

Метод может возвращать некоторое значение. Тогда возвращаемое значение оборачивается в объект **Task**, а возвращаемым типом является `Task<T>`:

```cs
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<int> FactorialAsync(int n)
{
    return await Task.Run(()=>Factorial(n));
}
```

В данном случае функция *Factorial* возвращает значение типа **int**. В асинхронном методе *FactorialAsync* мы получаем и **возвращаем** это число. Поэтому возвращаемым типом в данном случае является типа `Task<int>`. Если бы метод *Factorial* возвращал строку, то есть данные типа **string**, то возвращаемым типом асинхронного метода был бы тип `Task<string>`

Чтобы получить результат асинхронного метода в методе _Main_, который тоже определен как асинхронный, применяем оператор **await** при вызове *FactorialAsync*.

#### `ValueTask<T>`

Использование типа `ValueTask<T>` во многом аналогично применению `Task<T>` за исключением некоторых различий в работе с памятью, поскольку **ValueTask** - структура, а **Task** - класс. По умолчанию тип **ValueTask** недоступен, и чтобы использовать его, вначале надо установить через **NuGet** пакет `System.Threading.Tasks.Extensions`.

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

Асинхронный метод может содержать множество выражений **await**. Когда система встречает в блоке кода оператор **await**, то выполнение в асинхронном методе останавливается, пока не завершится асинхронная задача. После завершения задачи управление переходит к следующему оператору **await** и так далее. Это позволяет вызывать асинхронные задачи последовательно в определенном порядке. Например:

```cs
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));
}
```

Консольный вывод данной программы:

```
Факториал числа 4 равен 24
Факториал числа 3 равен 6
Факториал числа 5 равен 120
```

То есть мы видим, что факториалы вычисляются последовательно. И в данном случае вывод строго детерминирован.

Нередко такая последовательность бывает необходима, если одна задача зависит от результатов другой.

Однако не всегда существует подобная зависимость между задачами. В этом случае мы можем запустить все задачи параллельно и через метод *Task.WhenAll* отследить их завершение. Например, изменим метод *FactorialAsync*:

```cs
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 });
}
```

Вначале запускаются три задачи. Затем *Task.WhenAll* создает новую задачу, которая будет автоматически выполнена после выполнения всех предоставленных задач, то есть задач `t1`, `t2`, `t3`. А с помощью оператора **await** ожидаем ее завершения.

В итоге все три задачи теперь будут запускаться параллельно, однако вызывающий метод *FactorialAsync* благодаря оператору **await** все равно будет ожидать, пока они все не завершатся. И в этом случае вывод программы не детерминирован. Например, он может быть следующим:

```
Факториал числа 5 равен 120
Факториал числа 4 равен 24
Факториал числа 3 равен 6
```

И если задача возвращает какое-нибудь значение, то это значение потом можно получить с помощью свойства Result.

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

Обработка ошибок в асинхронных методах, использующих ключевые слова **async** и **await**, имеет свои особенности.

Для обработки ошибок выражение **await** помещается в блок **try**:

```cs
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);
    }
}
```

В данном случае метод *Factorial* генерирует исключение, если методу передается число меньше `1`.

Для обработки исключения в методе *FactorialAsync* выражение **await** помещено в блок **try**.

В методе *Main* вызывается асинхронный метод с передачей ему отрицательного числа: `FactorialAsync(-4)`, что привет к генерации исключения. Однако программа не остановит аварийно свою работу, а обработает исключение и продолжит дальнейшие вычисления.

Следует учитывать, что если мы запускаем данный код в режиме отладки в Visual Studio, то VS просигнализирует нам о генерации исключении и остановит выполнение на строке `throw new Exception($"{n} : число не должно быть меньше 1");`. В режиме запуска без отладки VS не будет останавливаться.

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

При возникновении ошибки у объекта **Task**, представляющего асинхронную задачу, в которой произошла ошибка, свойство *IsFaulted* имеет значение **true**. Кроме того, свойство *Exception* объекта **Task** содержит всю информацию об ошибке. Чтобы проинспектировать свойство, изменим метод *FactorialAsync* следующим образом:

```cs
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

Если мы ожидаем выполнения сразу нескольких задач, например, с помощью *Task.WhenAll*, то мы можем получить сразу несколько исключений одномоментно для каждой выполняемой задачи. В этом случае мы можем получить все исключения из свойства *Exception.InnerExceptions*:

```cs
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* мы сможем получить инфрмацию обо всех возникших исключениях.

В итоге при выполнении этого метода мы получим следующий консольный вывод:

```
Исключение: -3 : число не должно быть меньше 1
IsFaulted: True
Внутреннее исключение: -3: число не должно быть меньше 1
Внутреннее исключение: -5: число не должно быть меньше 1
Внутреннее исключение: -10: число не должно быть меньше 1
```

#### await в блоках catch и finally

Начиная с версии C# 6.0 в язык была добавлена возможность вызова асинхронного кода в блоках **catch** и **finally**. Так, возьмем предыдущий пример с подсчетом факториала:

```cs
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*.

Рассмотрим применение этих классов на примере:

```cs
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));
}
```

Для создания токена определяется объект **CancellationTokenSource**. Метод *FactorialAsync* в качестве параметра принимает токен, и если где-то во внешнем коде произойдет отмена операции через вызов *cts.Cancel*, то в методе *Factorial* свойство *token.IsCancellationRequested* будет равно **true**, и соответственно при очередной итерации цикла в методе **Factorial** произойдет выход из метода. И асинхронная операция завершится. И мы получим следующий консольный вывод:

```
Факториал числа 1 равен 1
Факториал числа 2 равен 2
Факториал числа 3 равен 6
Операция была отменена.
```

---

## Задание на дом:

Реализовать примеры из лекции. Привести текст примера и текст результата, например:

># Конспект лекции "Многопоточность"
>
>## Варианты создания и запуска задач
>
>```cs
>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();
>```
>
>```
>/home/kei/RiderProjects/asyncAwait/bin/Debug/net8.0/asyncAwait
>Task2 is executed
>Task1 is executed
>Task3 is executed
>
>Process finished with exit code 0.
>```

Предыдущая лекция | &nbsp; | Следующая лекция
:----------------:|:----------:|:----------------:
[Исключения. Null.](./t5_exception.md) | [Содержание](../readme.md#тема-5-продвинутый-c-функции-лямбды-исключения-работа-с-файлами-многопоточность-регулярные-выражения) | [Работа с файловой системой и файлами.](./t5_files.md)
