Ответ
Отмена задач в 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 в глубину вызовов.
Ответ 18+ 🔞
Ну что за дичь, опять эти токены отмены! Слушай, я тебе сейчас на пальцах объясню, как эту хрень использовать, чтобы не выстрелить себе в ногу.
Вот смотри, в C# тебе не дадут просто так взять и убить задачу — это не какой-то там варварский Thread.Abort(), который всё ломает к чертям. Тут всё по-взрослому, через вежливые уведомления. Есть два главных героя: CancellationTokenSource (тот, кто кричит "отмена!") и сам CancellationToken (флажок, который ты передаёшь в задачу).
1. Базовый сценарий, чтоб ты понял суть
// Это наш пульт управления отменой, с красной кнопкой
var cts = new CancellationTokenSource();
// А это сам флажок-уведомление, который мы будем тыкать в задачу
var token = cts.Token;
var task = Task.Run(() =>
{
for (int i = 0; i < 1000; i++)
{
// Способ первый, радикальный: проверяем и сразу в исключение
token.ThrowIfCancellationRequested(); // Если отмена запрошена — летит OperationCanceledException
// Способ второй, аккуратный: проверили и вышли по-тихому
if (token.IsCancellationRequested)
{
Console.WriteLine("Всё, приехали, завершаемся.");
// Тут можно прибраться за собой — закрыть файлы, отключиться от БД
return; // Просто выходим, задача будет считаться успешно завершённой
}
// Делаем вид, что работаем
Thread.Sleep(100);
Console.WriteLine($"Кручу-верчу, итерация {i}");
}
}, token); // ОБЯЗАТЕЛЬНО суём токен сюда, это важно для правильного статуса задачи
// Допустим, прошло 2 секунды и нам надоело ждать
cts.CancelAfter(2000);
// Или можем отменить сразу: cts.Cancel();
try
{
await task; // Если задача отменена через ThrowIfCancellationRequested, тут будет исключение
}
catch (OperationCanceledException)
{
Console.WriteLine("Задачу отменили, как и просили.");
}
2. Таймаут — чтоб не ждать до второго пришествия
// Можно сразу создать источник с таймаутом
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
await SomeLongOperation(cts.Token);
// Или назначить таймаут потом
cts.CancelAfter(5000); // Через 5 секунд само отменится
3. Объединение токенов — когда на тебя орут сразу несколько
Бывает, что задача должна слушаться и пользователя (нажал "отмена"), и общего таймаута системы.
var userCts = new CancellationTokenSource(); // Пользовательская отмена
var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); // Системный таймаут
// Связываем их вместе — сработает любой
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
userCts.Token,
timeoutCts.Token
);
await SuperImportantProcess(linkedCts.Token); // Передаём объединённый токен
4. Асинхронные операции — там всё уже готово
Все нормальные асинхронные методы в .NET (HttpClient, EF Core, файловые операции) принимают CancellationToken. Пользуйся!
public async Task<string> DownloadWithTimeout(string url, CancellationToken ct)
{
using var httpClient = new HttpClient();
// Передаём токен прямо в GetAsync — если отменят, запрос прервётся
using var response = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, ct);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync(ct); // И здесь тоже!
}
// Используем
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
try
{
var data = await DownloadWithTimeout("https://example.com", cts.Token);
}
catch (TaskCanceledException) // HttpClient кидает именно TaskCanceledException при отмене
{
Console.WriteLine("Не успел, блядь, отменили по таймауту!");
}
5. Важный нюанс про состояние задачи
Запомни раз и навсегда:
- Если задача упала через
ThrowIfCancellationRequested()— её статус будетTaskStatus.Canceled. - Если она сама проверила
IsCancellationRequestedи вышла черезreturn— статусTaskStatus.RanToCompletion(успех!). - Задачу в состоянии
CanceledилиFaultedнельзя повторноawaitить — получишь исключение, как от скучной лекции.
И главный совет: всегда, блядь, проектируй свои долгие операции с возможностью отмены и прокидывай CancellationToken вглубь всех вызовов. Это признак хорошего тона, а не просто прихоть.