vivanov 3511166805 first commit | 10 mēneši atpakaļ | |
---|---|---|
.gitignore.txt | 11 mēneši atpakaļ | |
readmee.md | 10 mēneši atpakaļ |
Введение в многопоточность. Класс Thread
Класс Thread в C# позволяет создавать и управлять потоками выполнения в приложении. Это позволяет выполнять различные задачи одновременно, так же оптимизирует программу.
Параллельное программирование и библиотека TPL
Данная библиотека позволяет распараллелить задачи и выполнять их сразу на нескольких процессорах, если на целевом компьютере имеется несколько ядер. Кроме того, упрощается сама работа по созданию новых потоков.
Kласс Task
Данный класс описывает отдельную задачу, которая запускается асинхронно в одном из потоков из пула потоков. Хотя ее также можно запускать синхронно в текущем потоке.
Первый способ
Task task = new Task(
() => Console.WriteLine("Hello Task!")
);
task.Start();
Он нам ничего не выведет, так как программа завершается быстрее, чем запуск потока.
pa.detach.10176 C:/Users/шоумен/RiderProjects/ConsoleApp7/ConsoleApp7/bin/Debug/net8.0/ConsoleApp7.exe
Process finished with exit code 0.
Второй способ
Этот метот сразу же запускает задачу.
Task task = Task.Factory.StartNew(
() => Console.WriteLine("Hello Task!")
);
В качестве результата метод возвращает запущенную задачу.
Третий способ
Task task = Task.Run(
() => Console.WriteLine("Hello Task!")
);
Выполняет действие и возвращает объект Task.
Пример 1
Важно понимать, что задачи не выполняются последовательно.Первая запущенная задача может завершить свое выполнение после последней задачи.
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();
Вывод
Task1 is executed
Task2 is executed
Task3 is executed
Пример 2
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");
}
Вывод
Завершение метода Main
Начало работы метода Display
Завершение работы метода Display
Метод Wait
Чтобы указать, что метод Main должен подождать до конца выполнения задачи, нам надо использовать метод Wait:
Task task = new Task(Display);
task.Start();
task.Wait();
Console.WriteLine("Завершение метода Main");
Console.ReadLine();
Свойства класса Task
Aсинхронное программирование. Асинхронные методы, async и await
Асинхронность позволяет вынести отдельные задачи из основного потока в специальные асинхронные методы или блоки кода.
Ключевыми для работы с асинхронными вызовами в C# являются два ключевых слова: async и await, цель которых - упростить написание асинхронного кода. Они используются вместе для создания асинхронного метода.
Асинхонный метод обладает следующими признаками:
В заголовке метода используется модификатор async
Метод содержит одно или несколько выражений await
В качестве возвращаемого типа используется один из следующих:
Пример асинхронного метода:
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
Введите число:
3
Квадрат числа равен 9
Вывод 2
Введите число:
7
Квадрат числа равен 49
Факториал равен 720
Конец метода FactorialAsync
Разберем поэтапно, что здесь происходит:
Запускается метод Main, в котором вызывается асинхронный метод FactorialAsync.
Метод FactorialAsync начинает выполняться синхронно вплоть до выражения await.
Выражение await запускает асинхронную задачу Task.Run(()=>Factorial())
Пока выполняется асинхронная задача Task.Run(()=>Factorial()) (а она может выполняться довольно продожительное время), выполнение кода возвращается в вызывающий метод - то есть в метод Main. В методе Main нам будет предложено ввести число для вычисления квадрата числа.
В этом и преимущество асинхронных методов - асинхронная задача, которая может выполняться довольно долгое время, не блокирует метод Main, и мы можем продолжать работу с ним, например, вводить и обрабатывать данные.
Когда асинхронная задача завершила свое выполнение (в случае выше - подсчитала факториал числа), продолжает работу асинхронный метод FactorialAsync, который вызвал асинхронную задачу.
Другой пример - чтение-запись файла:
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);
}
}
Вывод
Некоторая работа
Hello world! One step at a time
Определение асинхронной операции
Mожем определить асинхронную операцию, используя метод Task.Run():
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());
}
Можно определить асинхронную операцию с помощью лямбда-выражения:
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, но, допустим, мы хотим вычислять факториалы разных чисел:
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));
}
Получение результата из асинхронной операции
Асинхронная операция может возвращать некоторый результат, получить который мы можем так же, как и при вызове обычного метода:
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<T>
или ValueTask<T>
void
При использовании ключевого слова void асинхронный метод ничего не возвращает:
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
:
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<T>
:
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));
}
ValueTask
Использование типа ValueTask<T>
во многом аналогично применению Task<T>
за исключением некоторых различий в работе с памятью, поскольку ValueTask
- структура, а Task
- класс. По умолчанию тип ValueTask
недоступен, и чтобы использовать его, вначале надо установить через NuGet пакет System.Threading.Tasks.Extensions.
Последовательный и параллельный вызов асинхронных операций
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
То есть мы видим, что факториалы вычисляются последовательно. И в данном случае вывод строго детерминирован.
Нередко такая последовательность бывает необходима, если одна задача зависит от результатов другой. Например, изменим метод FactorialAsync:
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 });
}
Вывод будет следующим
Факториал числа 5 равен 120
Факториал числа 4 равен 24
Факториал числа 3 равен 6
Обработка ошибок в асинхронных методах
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);
}
}
Вывод
Факториал числа 6 равен 720
-4 : число не должно быть меньше 1
Исследование исключения
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
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
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.
Рассмотрим применение этих классов на примере:
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));
}
Вывод
Факториал числа 1 равен 1
Факториал числа 2 равен 2
Факториал числа 3 равен 6
Операция была отменена.