Работа c файловой системой. | Содержание | Форматы файлов |
Одним из ключевых аспектов в современном программировании является многопоточность. Ключевым понятием при работе с многопоточностью является поток. Поток предствляет некоторую часть кода программы. При выполнении программы каждому потоку выделяется определенный квант времени. И при помощи многопоточности мы можем выделить в приложении несколько потоков, которые будут выполнять различные задачи одновременно. Если у нас, допустим, графическое приложение, которое посылает запрос к какому-нибудь серверу или считывает и обрабатывает огромный файл, то без многопоточности у нас бы блокировался графический интерфейс на время выполнения задачи. А благодаря потокам мы можем выделить отправку запроса или любую другую задачу, которая может долго обрабатываться, в отдельный поток. Поэтому, к примеру, клиент-серверные приложения (и не только они) практически не мыслимы без многопоточности.
Основной функционал для использования потоков в приложении сосредоточен в пространстве имен System.Threading. В нем определен класс, представляющий отдельный поток - класс Thread.
В эпоху многоядерных машин, которые позволяют параллельно выполнять сразу несколько процессов, стандартных средств работы с потоками в .NET уже оказалось недостаточно. Поэтому во фреймворк .NET была добавлена библиотека параллельных задач TPL (Task Parallel Library), основной функционал которой располагается в пространстве имен System.Threading.Tasks. Данная библиотека позволяет распараллелить задачи и выполнять их сразу на нескольких процессорах, если на целевом компьютере имеется несколько ядер. Кроме того, упрощается сама работа по созданию новых потоков. Поэтому начиная с .NET 4.0. рекомендуется использовать именно TPL и ее классы для создания многопоточных приложений, хотя стандартные средства и класс Thread по-прежнему находят широкое применение.
В основе библиотеки TPL лежит концепция задач, каждая из которых описывает отдельную продолжительную операцию. В библиотеке классов .NET задача представлена специальным классом - классом Task, который находится в пространстве имен System.Threading.Tasks. Данный класс описывает отдельную задачу, которая запускается асинхронно в одном из потоков из пула потоков. Хотя ее также можно запускать синхронно в текущем потоке.
Для определения и запуска задачи можно использовать различные способы. Первый способ создание объекта Task и вызов у него метода Start:
Task task = new Task(() => Console.WriteLine("Hello Task!"));
task.Start();
В качестве параметра объект Task принимает делегат Action, то есть мы можем передать любое действие, которое соответствует данному делегату, например, лямбда-выражение, как в данном случае, или ссылку на какой-либо метод. То есть в данном случае при выполнении задачи на консоль будет выводиться строка "Hello Task!".
А метод Start() собственно запускает задачу.
Второй способ заключается в использовании статического метода Task.Factory.StartNew(). Этот метод также в качестве параметра принимает делегат Action, который указывает, какое действие будет выполняться. При этом этот метод сразу же запускает задачу:
Task task = Task.Factory.StartNew(() => Console.WriteLine("Hello Task!"));
В качестве результата метод возвращает запущенную задачу.
Третий способ определения и запуска задач представляет использование статического метода Task.Run():
Task task = Task.Run(() => Console.WriteLine("Hello Task!"));
Метод Task.Run() также в качестве параметра может принимать делегат Action - выполняемое действие и возвращает объект Task.
Определим небольшую программу, где используем все эти способы:
using System;
using System.Threading.Tasks;
namespace HelloApp
{
class Program
{
static void Main(string[] args)
{
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();
}
}
}
Важно понимать, что задачи не выполняются последовательно. Первая запущенная задача может завершить свое выполнение после последней задачи.
Или рассмотрим еще один пример:
using System;
using System.Threading;
namespace TaskApp
{
class Program
{
static void Main(string[] args)
{
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:
static void Main(string[] args)
{
Task task = new Task(Display);
task.Start();
task.Wait();
Console.WriteLine("Завершение метода Main");
Console.ReadLine();
}
Класс Task имеет ряд свойств, с помощью которых мы можем получить информацию об объекте. Некоторые из них:
-
AsyncState: возвращает объект состояния задачи
-
CurrentId: возвращает идентификатор текущей задачи
-
Exception: возвращает объект исключения, возникшего при выполнении задачи
-
Status: возвращает статус задачи
Асинхронность позволяет вынести отдельные задачи из основного потока в специальные асинхронные методы или блоки кода. Особенно это актуально в графических программах, где продолжительные задачи могу блокировать интерфейс пользователя. И чтобы этого не произошло, нужно задействовать асинхронность. Также асинхронность несет выгоды в веб-приложениях при обработке запросов от пользователей, при обращении к базам данных или сетевым ресурсам. При больших запросах к базе данных асинхронный метод просто уснет на время, пока не получит данные от БД, а основной поток сможет продолжить свою работу. В синхронном же приложении, если бы код получения данных находился в основном потоке, этот поток просто бы блокировался на время получения данных.
Ключевыми для работы с асинхронными вызовами в C# являются два ключевых слова: async и await, цель которых - упростить написание асинхронного кода. Они используются вместе для создания асинхронного метода.
Асинхонный метод обладает следующими признаками:
-
В заголовке метода используется модификатор async
-
Метод содержит одно или несколько выражений await
-
В качестве возвращаемого типа используется один из следующих:
- void
- Task
- Task<T>
- ValueTask<T>
Асинхронный метод, как и обычный, может использовать любое количество параметров или не использовать их вообще. Однако асинхронный метод не может определять параметры с модификаторами out и ref.
Также стоит отметить, что слово async, которое указывается в определении метода, не делает автоматически метод асинхронным. Оно лишь указывает, что данный метод может содержать одно или несколько выражений await.
Рассмотрим пример асинхронного метода:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace HelloApp
{
class Program
{
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");
}
static void Main(string[] args)
{
FactorialAsync(); // вызов асинхронного метода
Console.WriteLine("Введите число: ");
int n = Int32.Parse(Console.ReadLine());
Console.WriteLine($"Квадрат числа равен {n * n}");
Console.Read();
}
}
}
Здесь прежде всего определен обычный метод подсчета факториала. Для имитации долгой работы в нем используется задержка на 8 секунд с помощью метода Thread.Sleep()
. Условно это некоторый метод, который выполняет некоторую работу продолжительное время. Но для упрощения понимания он просто подсчитывает факториал числа 6.
Также здесь определен асинхронный метод FactorialAsync(). Асинхронным он является потому, что имеет в определении перед возвращаемым типом модификатор async, его возвращаемым типом является void, и в теле метода определено выражение await.
Выражение await определяет задачу, которая будет выполняться асинхронно. В данном случае подобная задача представляет выполнение функции факториала:
await Task.Run(()=>Factorial());
По негласным правилам в названии асинхроннных методов принято использовать суффикс Async - FactorialAsync(), хотя в принципе это необязательно делать.
Сам факториал мы получаем в асинхронном методе FactorialAsync. Асинхронным он является потому, что он объявлен с модификатором async и содержит использование ключевого слова await.
И в методе Main мы вызываем этот асинхронный метод.
Посмотрим, какой у программы будет консольный вывод:
Начало метода FactorialAsync
Введите число:
7
Квадрат числа равен 49
Конец метода Main
Факториал равен 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;
namespace HelloApp
{
class Program
{
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);
}
}
static void Main(string[] args)
{
ReadWriteAsync();
Console.WriteLine("Некоторая работа");
Console.Read();
}
}
}
Асинхронный метод ReadWriteAsync() выполняет запись в файл некоторой строки и затем считывает записанный файл. Подобные операции могут занимать продолжительное время, особенно при больших объемах данных, поэтому такие операции лучше делать асинхронными.
Фреймворк .NET уже имеет встроенную поддержку таких операций. Например, в классе StreamWriter определен метод WriteLineAsync(). По сути он уже представляет асинхронную операцию и принимает в качестве параметра некоторую строку, которую надо записать в файл. Поскольку этот метод представляет асинхронную операцию, то вызов этого метода мы можем оформить в выражение await:
await writer.WriteLineAsync(s); // асинхронная запись в файл
Аналогично в классе StreamReader определен метод ReadToEndAsync(), который также представляет асинхронную операцию и который возвращает весь считанный текст.
Во фреймворке определено много подобных методов. Как правило, они связаны с работой с файлами, отправкой сетевых запросов или запросов к базе данных. Их легко узнать по суффиксу Async. То есть если метод имеет подобный суффикс в названии, то с большей степенью вероятности его можно использовать в выражении await.
Далее в методе Main вызывается асинхронный метод ReadWriteAsync:
static void Main(string[] args)
{
ReadWriteAsync();
Console.WriteLine("Некоторая работа");
Console.Read();
}
И опять же, когда выполнение в методе ReadWriteAsync доходит до первого выражения await, управление возвращается в метод Main, и мы можем продолжать с ним работу. Запись в файл и считывание файла будут производиться параллельно и не будут блокировать работу метода Main.
Как выше уже было сказано, фреймворк .NET имеет много встроенных методов, которые представляют асинхронную операцию. Они заканчиваются на суффикс Async. И перед вызывами подобных методов мы можем указывать оператор await. Например:
StreamWriter writer = new StreamWriter("hello.txt", false);
await writer.WriteLineAsync("Hello"); // асинхронная запись в файл
Либо мы сами можем определить асинхронную операцию, используя метод 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;
namespace HelloApp
{
class Program
{
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));
}
static void Main(string[] args)
{
FactorialAsync(5);
FactorialAsync(6);
Console.WriteLine("Некоторая работа");
Console.Read();
}
}
}
Асинхронная операция может возвращать некоторый результат, получить который мы можем так же, как и при вызове обычного метода:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace HelloApp
{
class Program
{
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}");
}
static void Main(string[] args)
{
FactorialAsync(5);
FactorialAsync(6);
Console.Read();
}
}
}
Метод Factorial возвращает значение типа int, это значение мы можем получить, просто присвоив результат асинхронной операции переменной данного типа: int x = await Task.Run(()=>Factorial(n));
В качестве возвращаемого типа в асинхронном методе должны использоваться типы void, Task, Task<T> или ValueTask<T>
При использовании ключевого слова 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:
using System;
using System.Threading.Tasks;
namespace HelloApp
{
class Program
{
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));
}
static void Main(string[] args)
{
FactorialAsync(5);
FactorialAsync(6);
Console.WriteLine("Некоторая работа");
Console.Read();
}
}
}
Формально метод FactorialAsync не использует оператор return для возвращения результата. Однако если в асинхронном методе выполняется в выражении await асинхронная операция, то мы можем возвращать из метода объект Task.
Метод может возвращать некоторое значение. Тогда возвращаемое значение оборачивается в объект Task, а возвращаемым типом является Task<T>:
using System;
using System.Threading.Tasks;
namespace HelloApp
{
class Program
{
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));
}
static async Task Main(string[] args)
{
int n1 = await FactorialAsync(5);
int n2 = await FactorialAsync(6);
Console.WriteLine($"n1={n1} n2={n2}");
Console.Read();
}
}
}
В данном случае функция Factorial возвращает значение типа int. В асинхронном методе FactorialAsync мы получаем и возвращаем это число. Поэтому возвращаемым типом в данном случае является типа Task<int>. Если бы метод Factorial возвращал строку, то есть данные типа string, то возвращаемым типом асинхронного метода был бы тип Task<string>
Чтобы получить результат асинхронного метода в методе Main, который тоже определен как асинхронный, применяем оператор await при вызове FactorialAsync.
Использование типа ValueTask<T> во многом аналогично применению Task<T> за исключением некоторых различий в работе с памятью, поскольку ValueTask - структура, а Task - класс. По умолчанию тип ValueTask недоступен, и чтобы использовать его, вначале надо установить через NuGet пакет System.Threading.Tasks.Extensions.
Использование ValueTask:
using System;
using System.Threading.Tasks;
namespace HelloApp
{
class Program
{
static int Factorial(int n)
{
int result = 1;
for (int i = 1; i <= n; i++)
{
result *= i;
}
return result;
}
// определение асинхронного метода
static async ValueTask<int> FactorialAsync(int n)
{
return await Task.Run(()=>Factorial(n));
}
static async Task Main(string[] args)
{
int n1 = await FactorialAsync(5);
int n2 = await FactorialAsync(6);
Console.WriteLine($"n1={n1} n2={n2}");
Console.Read();
}
}
}
Асинхронный метод может содержать множество выражений await. Когда система встречает в блоке кода оператор await, то выполнение в асинхронном методе останавливается, пока не завершится асинхронная задача. После завершения задачи управление переходит к следующему оператору await и так далее. Это позволяет вызывать асинхронные задачи последовательно в определенном порядке. Например:
using System;
using System.Threading.Tasks;
namespace HelloApp
{
class Program
{
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));
}
static void Main(string[] args)
{
FactorialAsync();
Console.Read();
}
}
}
Консольный вывод данной программы:
Факториал числа 4 равен 24
Факториал числа 3 равен 6
Факториал числа 5 равен 120
То есть мы видим, что факториалы вычисляются последовательно. И в данном случае вывод строго детерминирован.
Нередко такая последовательность бывает необходима, если одна задача зависит от результатов другой.
Однако не всегда существует подобная зависимость между задачами. В этом случае мы можем запустить все задачи параллельно и через метод Task.WhenAll отследить их завершение. Например, изменим метод 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 });
}
Вначале запускаются три задачи. Затем Task.WhenAll создает новую задачу, которая будет автоматически выполнена после выполнения всех предоставленных задач, то есть задач t1, t2, t3. А с помощью оператора await ожидаем ее завершения.
В итоге все три задачи теперь будут запускаться параллельно, однако вызывающий метод FactorialAsync благодаря оператору await все равно будет ожидать, пока они все не завершатся. И в этом случае вывод программы не детерминирован. Например, он может быть следующим:
Факториал числа 5 равен 120
Факториал числа 4 равен 24
Факториал числа 3 равен 6
И если задача возвращает какое-нибудь значение, то это значение потом можно получить с помощью свойства Result.
Обработка ошибок в асинхронных методах, использующих ключевые слова async и await, имеет свои особенности.
Для обработки ошибок выражение await помещается в блок try:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace HelloApp
{
class Program
{
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);
}
}
static void Main(string[] args)
{
FactorialAsync(-4);
FactorialAsync(6);
Console.Read();
}
}
}
В данном случае метод Factorial генерирует исключение, если методу передается число меньше 1.
Для обработки исключения в методе FactorialAsync выражение await помещено в блок try.
В методе Main вызывается асинхронный метод с передачей ему отрицательного числа: FactorialAsync(-4)
, что привет к генерации исключения. Однако программа не остановит аварийно свою работу, а обработает исключение и продолжит дальнейшие вычисления.
Следует учитывать, что если мы запускаем данный код в режиме отладки в Visual Studio, то VS просигнализирует нам о генерации исключении и остановит выполнение а строке throw new Exception($"{n} : число не должно быть меньше 1");. В режиме запуска без отладки VS не будет останавливаться.
При возникновении ошибки у объекта Task, представляющего асинхронную задачу, в которой произошла ошибка, свойство IsFaulted имеет значение true. Кроме того, свойство Exception объекта Task содержит всю информацию об ошибке. Чтобы проинспектировать свойство, изменим метод FactorialAsync следующим образом:
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.
Если мы ожидаем выполнения сразу нескольких задач, например, с помощью Task.WhenAll, то мы можем получить сразу несколько исключений одномоментно для каждой выполняемой задачи. В этом случае мы можем получить все исключения из свойства Exception.InnerExceptions:
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
Начиная с версии C# 6.0 в язык была добавлена возможность вызова асинхронного кода в блоках 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;
namespace HelloApp
{
class Program
{
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));
}
static void Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
FactorialAsync(6, token);
Thread.Sleep(3000);
cts.Cancel();
Console.Read();
}
}
}
Для создания токена определяется объект CancellationTokenSource. Метод FactorialAsync в качестве параметра принимает токен, и если где-то во внешнем коде произойдет отмена операции через вызов cts.Cancel, то в методе Factorial свойство token.IsCancellationRequested будет равно true, и соответственно при очередной итерации цикла в методе Factorial произойдет выход из метода. И асинхронная операция завершится. И мы получим следующий консольный вывод:
Факториал числа 1 равен 1
Факториал числа 2 равен 2
Факториал числа 3 равен 6
Операция была отменена.
Работа c файловой системой. | Содержание | Форматы файлов |