Ответ
Отмена задач в C# реализуется через кооперативную модель с использованием CancellationTokenSource и CancellationToken. Задача не прерывается принудительно, а лишь получает уведомление о запросе отмены и должна корректно завершиться.
1. Базовый паттерн отмены
// Источник токена, который инициирует отмену
var cancellationTokenSource = new CancellationTokenSource();
// Сам токен, который передается в задачу
CancellationToken cancellationToken = cancellationTokenSource.Token;
// Задача, которая отслеживает запрос на отмену
var longRunningTask = Task.Run(() =>
{
for (int i = 0; i < 1000; i++)
{
// Способ 1: Выбросить OperationCanceledException
cancellationToken.ThrowIfCancellationRequested();
// Способ 2: Проверить флаг и выйти корректно
if (cancellationToken.IsCancellationRequested)
{
// Выполнить cleanup (закрыть файлы, соединения)
Console.WriteLine("Задача завершается по отмене.");
return; // Просто выходим из метода, задача перейдет в состояние RanToCompletion
}
// Полезная работа
Thread.Sleep(100); // Имитация работы
Console.WriteLine($"Итерация {i}");
}
}, cancellationToken); // Важно передать токен в Task.Run для привязки
// ... где-то в другом месте (по кнопке пользователя, таймауту)
cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(2)); // Автоотмена через 2 сек
// или
// cancellationTokenSource.Cancel(); // Немедленная отмена
try
{
await longRunningTask; // Если задача отменена через ThrowIfCancellationRequested,
// здесь будет выброшено AggregateException -> OperationCanceledException
}
catch (OperationCanceledException)
{
Console.WriteLine("Задача была отменена.");
}
2. Отмена с таймаутом
// Создать источник с таймаутом при создании
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
await SomeAsyncOperation(cts.Token);
// Или установить таймаут позже
cts.CancelAfter(5000);
3. Объединение нескольких токенов Полезно, когда задача должна отреагировать на отмену из нескольких источников (например, пользовательский запрос + общий таймаут).
var userCancellationSource = new CancellationTokenSource();
var timeoutSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
// Создаем linked token source, который сработает при отмене любого из токенов
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
userCancellationSource.Token,
timeoutSource.Token
);
await ProcessDataAsync(linkedCts.Token);
4. Отмена асинхронных операций (HttpClient, EF Core)
Большинство современных асинхронных API в .NET принимают CancellationToken.
public async Task<string> DownloadWithTimeoutAsync(string url, CancellationToken cancellationToken)
{
using var httpClient = new HttpClient();
using var response = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync(cancellationToken);
}
// Использование
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
try
{
string data = await DownloadWithTimeoutAsync("https://api.example.com/data", cts.Token);
}
catch (TaskCanceledException) // HttpClient выбрасывает TaskCanceledException при отмене
{
Console.WriteLine("Запрос отменен по таймауту.");
}
5. Состояние задачи после отмены
- Если задача завершилась вызовом
ThrowIfCancellationRequested(), ее свойствоStatusбудет равноTaskStatus.Canceled. - Если задача проверила
IsCancellationRequestedи вышла черезreturn, ее статус будетTaskStatus.RanToCompletion. - Задачу в состоянии
CanceledилиFaultedнельзяawaitить повторно — будет исключение.
Главное правило: Всегда проектируйте длительные операции с возможностью кооперативной отмены и передавайте CancellationToken в глубину вызовов.