Привет, Хабр! Меня зовут Давид, я C#-разработчик в SimbirSoft. В современном программировании эффективное управление ресурсами и контроль за выполнением задач становятся ключевыми аспектами для создания надежных и масштабируемых приложений. В C# одним из важнейших инструментов для достижения этих целей является механизм Cancellation Token. Эта концепция позволяет разработчикам изящно и безопасно управлять долгосрочными или ресурсоемкими операциями, обеспечивая возможность их отмены по требованию.
В этой статье мы погрузимся в мир Cancellation Token в C#, исследуя его роль, принципы работы и практическое применение. Мы обсудим, как эта технология позволяет разработчикам улучшить производительность и надежность их приложений, а также — как избежать распространенных ошибок при работе с асинхронными операциями. Подробно рассмотрим примеры кода, демонстрирующие использование Cancellation Token в различных сценариях, что сделает наше обсуждение не только теоретически насыщенным, но и довольно практичным для программистов любого уровня.
Основное назначение Cancellation Token
Cancellation Token в C# — это эффективный инструмент для управления операциями, которые могут затянуться или требуют асинхронного выполнения. В современном программировании, где время реакции и гибкость приложений играют ключевую роль, наличие механизма для остановки и корректировки длительных процессов становится не просто полезным, а критически важным. Cancellation Token предоставляет разработчикам элегантный способ для контроля над такими задачами, обеспечивая создание отзывчивых и адаптивных приложений.
Рассмотрим практический пример: пользователь начинает загрузку большого файла, но решает ее отменить до завершения. Без механизма Cancellation Token процесс загрузки продолжался бы, потребляя ценные ресурсы и снижая общую производительность системы. Аналогичная ситуация возникает при необходимости изменения параметров обработки данных: без возможности быстрой остановки текущей задачи приложение теряет в гибкости и скорости реагирования.
В контексте многопоточных приложений управление Cancellation Token приобретает еще большую значимость. Оно позволяет безопасно координировать работу множества параллельных задач, избегая проблем, связанных с блокировками и «мертвыми» состояниями потоков. Это не только улучшает производительность, но и повышает общую надежность приложений, делая их более устойчивыми к изменчивым условиям эксплуатации.
Принцип работы Cancellation Token
Cancellation Token в C# реализуется через два основных типа: структуру CancellationToken
и класс CancellationTokenSource
.
Объект CancellationTokenSource
выступает в роли командного центра. Он предоставляет get-only свойство Token, через которое можно получить связанный объект CancellationToken
. Управление процессом отмены осуществляется через методы Cancel()
и CancelAfter()
. Когда наступает момент остановить операцию, CancellationTokenSource
действует как регулятор, инициируя процесс отмены через вызов метода Cancel()
. Это приводит к тому, что все связанные с этим источником токены отправляют сигналы об отмене своим асинхронным задачам. Аналогичным образом действует метод CancelAfter()
, который выполняет то же действие через указанное в параметрах время.
Здесь следует уточнить механизм работы CancellationToken. Несмотря на то, что токен представляет собой структуру, по семантике он ведёт себя как класс. Сделано это было разработчиками следующим образом: в Это поле используется в проверке на равенство токенов и при копировании, так что копия токена ведёт себя как оригинал и тем самым неотличима от него. В частности, если оригинал токена отменён, то его копия — тоже. Причина же такой нестандартной конструкции кроется в эффективности работы с памятью: если бы Первое, что необходимо понимать о Cancellation Token — то, что он работает на основе кооперативной отмены. Это означает, что задача, которую нужно отменить, сама должна регулярно проверять состояние токена и корректно реагировать на запрос отмены. Когда дело доходит до отмены задачи, система не вмешивается автоматически, как некий всемогущий регулятор — задача должна «сотрудничать», чтобы отмена была безопасной и эффективной. Это похоже на водителя, который следит за дорожными знаками и сигналами светофора, чтобы знать, когда пора остановиться или изменить маршрут. Если задача игнорирует эти «дорожные знаки» отмены, она продолжит свое выполнение, даже если токен уже подает сигнал о необходимости остановки. Этот подход требует от разработчиков осознанного и аккуратного внедрения логики проверки и реагирования на Cancellation Token в свои асинхронные методы. Это означает, что каждая задача должна быть написана с учетом потенциальной необходимости ее безопасной и эффективной отмены async static void DoWorkWithOperationCancel(CancellationToken cancellationToken) { try { for (int i = 1; i != 10; i++) { cancellationToken.ThrowIfCancellationRequested(); Thread.Sleep(200); Console.WriteLine($"Work {i} done."); } } catch (OperationCanceledException) { Console.WriteLine("Operation was canceled"); } }
2. Этот тип исключения, являясь наследником OperationCanceledException, выступает как более конкретный сигнал о том, что задача, выполняемая в рамках В случаях, когда не происходит внутренней обработки исключения, задача может перейти в состояние В контексте разработки Web API на платформе ASP.NET Core обработка исключений играет ключевую роль в создании надежных и устойчивых к ошибкам приложений. Среди разнообразных подходов к обработке исключений можно выделить 1. Регистрация 2. Регистрация фильтра исключений в конфигурации контроллеров обеспечивает его применение ко всем действиям в рамках приложения. В данном разделе нашего обзора рассмотрим сценарии, где применение этого инструмента оказывается не только полезным, но и необходимым, а также случаи, в которых его использование может быть излишним или нежелательным. Сценарии, в которых Cancellation Token становится вашим союзником: 1. Длительные операции. Если ваше приложение выполняет операции, которые занимают значительное время (например, загрузка данных, обработка больших объемов информации), тогда использование Cancellation Token позволяет предоставить пользователю контроль над этими процессами и сохранить ресурсы для более важных задач. 2. Асинхронные и многопоточные операции. В среде, где несколько задач выполняются параллельно, Cancellation Token обеспечивает эластичность управления, позволяя оперативно останавливать отдельные задачи по требованию, тем самым улучшая отклик приложения и предотвращая потенциальные проблемы с производительностью. 3. Операции с возможностью отмены пользователем. В интерактивных приложениях, где пользователи имеют возможность отменить текущие операции (например, загрузку файла или выполнение поискового запроса), наличие механизма отмены через Cancellation Token является ключевым для создания гибкого и отзывчивого пользовательского интерфейса. Случаи, когда Cancellation Token может оказаться не на своем месте: 1. Краткосрочные или простые операции. Для очень быстрых или простых операций внедрение данного инструмента может быть излишним. В таких случаях добавление механизма отмены может привести к ненужному усложнению кода и снижению производительности. 2. Операции, где отмена неприемлема. Также не следует применять этот механизм в таких типах операций, как критические задачи обновления или записи данных, где отмена может привести к неконсистентному или поврежденному состоянию. 3. Задачи с обязательным выполнением. В случаях, когда задача должна быть доведена до конца без возможности отмены (как важные финансовые транзакции или операции с критически важными данными), применение Cancellation Token не только нежелательно, но и потенциально опасно. Использование Cancellation Token рекомендуется так же в некоторых паттернах проектирования, включая некоторые классические паттерны GoF (Command, State, Strategy), архитектурные стили (Produser-Consumer) и корпоративные шаблоны Фаулера (Unit of work, Repository): 1. Command Паттерн Command инкапсулирует запросы в виде объектов, позволяя гибко ими управлять. Cancellation Token в этом случае можно использовать для отмены уже запущенных команд, особенно если они выполняют длительные операции public class FilterCommand : ICommand { public void Execute(CancellationToken cancellationToken) { // Процесс фильтрации, который регулярно проверяет cancellationToken } }
2. State Паттерн State используется для управления состоянием объекта. Cancellation Token может быть полезен для прерывания операций в одном состоянии, когда происходит переход в другое состояние public class RunningState : IState { public void Enter(CancellationToken cancellationToken) { Task.Run(() => { while (!cancellationToken.IsCancellationRequested) { // Логика бега } }, cancellationToken); } }
3. Strategy Паттерн Strategy предоставляет механизм для выбора алгоритма выполнения во время выполнения программы. Cancellation Token в этом контексте позволяет отменить выполнение текущей стратегии, если условия изменяются или если выбрана новая стратегия public class DataAnalyzer { private IAnalysisStrategy _currentStrategy; public void SetStrategy(IAnalysisStrategy strategy, CancellationToken cancellationToken) { cancellationToken.Cancel(); // Отмена текущей стратегии _currentStrategy = strategy; // Запуск новой стратегии } }
4. Producer-Consumer Producer-Consumer не является отдельным паттерном, он скорее представляет собой архитектурный стиль, согласно которому одни потоки генерируют данные (producers), а другие потоки их обрабатывают (consumers). Cancellation Token позволяет безопасно и эффективно прерывать этот процесс public void StartProcessing(CancellationToken cancellationToken) { Task producer = ProduceVideoFrames(cancellationToken); Task consumer = ConsumeVideoFrames(cancellationToken); // ... }
5. Unit of Work Паттерн Unit of Work группирует несколько операций в одну логическую единицу, которая должна быть выполнена целиком. Использование Cancellation Token позволяет отменить всю единицу работы, если возникает ошибка или если пользователь решает прервать процесс public void ProcessTransaction(UnitOfWork unitOfWork, CancellationToken cancellationToken) { foreach (var operation in unitOfWork.Operations) { if (cancellationToken.IsCancellationRequested) { // Отмена остальных операций return; } operation.Execute(); } }
6. Repository Паттерн Repository используется для абстрагирования слоя доступа к данным в приложениях. Включение Cancellation Token в операции репозитория позволяет отменять запросы к базе данных или другим хранилищам данных, особенно в сценариях с длительными запросами или операциями public class DataRepository { public async Task<List<Data>> QueryDataAsync(QueryCriteria criteria, CancellationToken cancellationToken) { return await dbContext.Data.Where(criteria.Filter).ToListAsync(cancellationToken); } }
Несмотря на все преимущества использования Cancellation Token, неправильное его применение может привести к серьезным проблемам, таким как угрозы безопасности и утечки памяти https://mitesh1612.github.io/blog/2022/01/20/cancellation-tokens Гамма Э., Хелм Р., Джонсон Р., Влиссидес Дж. Приемы объектно-ориентированного проектирования. Паттерны проектирования. — СПб.: Питер, 2015. — 368 с.: ил. ISBN 978-5-496-00389-6 Фаулер, Мартин (1963-). Шаблоны корпоративных приложений [Текст] / Мартин Фаулер при участии Дейвида Райса [и др.]. — Испр. изд. — Москва [и др.] : Вильямс, 2017. — 539, [4] с. : ил., табл.; 24 см.; ISBN 978-5-8459-1611-2 : 300 экз Хоп, Грегор, Вульф, Бобби. Шаблоны интеграции корпоративных приложений. : Пер. с англ. М. : ООО ‘‘И.Д. Вильямс’’, 2007. 672 с. : ил. Парал. тит. англ. ISBN 978-5-8459-1146-9 (рус.) Frank Buschmann, Regine Meunier, Hans Rohnert, Peter Sommerlad, & Michael Stal. Pattern-Oriented Software Architecture. — Wiley, 1996, ISBN 0471958697 Джон Скит, C# для профессионалов. Тонкости программирования. — Вильямс, 2014. — 608 с.: ил. ISBN 978-5-8459-1909-0 Стивен Клири, Конкурентность в C#. Асинхронное, параллельное программирование. — Питер, 2020. — 272 с.: ил. ISBN 978-5-4461-1572-3 Мартин Роберт, Чистый код: создание, анализ и рефакторинг. Библиотека программиста. — Питер, 2020. — 464 с.: ил. ISBN 978-5-4461-0960-9 Спасибо за внимание! Больше авторских материалов для backend-разработчиков от моих коллег читайте в соцсетях SimbirSoft – ВКонтакте и Telegram.CancellationToken
, в свою очередь, представляет собой запрос на отмену операции. Его можно передавать асинхронным методам через параметры, что позволяет им отслеживать статус отмены CancellationTokenSource cts = new(); CancellationToken token = cts.Token; Task myTask = Task.Run(() => DoWork(token), token); cts.Cancel(); // cts.CancelAfter(1000);
CancellationToken
есть единственное поле private CancellationTokenSource m_source;
CancellationToken
был реализован как класс, каждый раз при создании нового токена происходила бы аллокация памяти в куче. Однако для структуры в большинстве случаев аллокации происходят в стеке (если токен является локальной переменной, а в куче только тогда, когда структура является частью другого объекта, хранящегося в куче), что, во-первых, быстрее само по себе, а во-вторых, позволяет серьезно уменьшить нагрузку на Garbage Collector
. В основном именно по этим причинам CancellationToken
был создан таким, какой он есть сейчас (и каким мы его любим).Кооперативная отмена
TaskCanceledException
чаще всего возникает во время запроса на отмену в ходе выполнения встроенного метода, который изначально поддерживает возможность отмены операций (например, Task.Delay()
). Task
, была отменена async static void DoWorkWithTaskCancel(CancellationToken cancellationToken) { try { for (int i = 1; i != 10; i++) { await Task.Delay(200, cancellationToken); Console.WriteLine($"Work {i} done."); } } catch (TaskCanceledException) { Console.WriteLine("Task was canceled"); } }
TaskStatus.Canceled
, если ее выполнение было прервано, или TaskStatus.RanToCompletion
, если задача завершилась до того, как был подан сигнал об отмене, или если вместо генерации исключения построена другая логика отмены операции. Это помогает программистам понимать текущее состояние задач и правильно обрабатывать различные сценарии завершения операций.Реализация в WebAPI
Middleware
и Filter
как наиболее эффективные инструменты для управления ошибками, связанными с отменой задач. Рассмотрим их подробнее:Middleware:
является ключевым компонентом в архитектуре Web API, обеспечивающим гибкую и мощную систему для обработки запросов. Этот инструмент служит центральным узлом в конвейере обработки запросов, позволяя разработчикам внедрять разнообразные функции, включая логирование, управление кэшем, динамическую модификацию ответов и многое другое. В данном случае нам интересна его способность перехватывать и обрабатывать исключения. Создание специального класса Middleware
с методом Invoke()
и использование конструкции try-catch
для обработки исключений OperationCanceledException
и TaskCanceledException
позволяет централизованно управлять ошибками отмены задач в приложении namespace WebApp.Middleware; public class TaskCancellationHandlingMiddleware(RequestDelegate next, ILogger<TaskCancellationHandlingMiddleware> logger) { public async Task Invoke(HttpContext context) { try { await next(context); } catch (Exception _) when (_ is OperationCanceledException or TaskCanceledException) { logger.LogInformation("Task canceled"); } } }
Middleware
в конвейере обработки запросов гарантирует, что каждый запрос будет проверен на наличие этих исключений.app.UseMiddleware<TaskCancellationHandlingMiddleware>();
Filter.
Использование фильтров исключений предоставляет мощный механизм для обработки ошибок на уровне контроллеров и действий. Создание класса, наследующего от ExceptionFilterAttribute
, и переопределение метода OnException
позволяет определить специфическую логику обработки для исключений, возникающих в результате отмены задач. Этот подход не только упрощает обработку исключений, но и позволяет настраивать отправляемые клиенту HTTP-ответы, что улучшает взаимодействие с пользователем при возникновении ошибок using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; namespace WebApp.Filters; public class TaskcanceledExceptionFilter(ILoggerFactory loggerFactory) : ExceptionFilterAttribute { private readonly ILogger _logger = loggerFactory.CreateLogger<TaskcanceledExceptionFilter>(); public override void OnException(ExceptionContext context) { if (context.Exception is OperationCanceledException or TaskCanceledException) { _logger.LogInformation("Request was canceled"); context.ExceptionHandled = true; context.Result = new StatusCodeResult(400); } } }
builder.Services.AddControllers(opt => opt.Filters.Add<TaskcanceledExceptionFilter>());
Условия применения Cancellation Token
Шаблоны проектирования и архитектурные принципы
Возможные угрозы безопасности и утечки памяти при использовании Cancellation Token
ссылка на оригинал статьи https://habr.com/ru/articles/825386/
Добавить комментарий