TGViewer
.NET Разработчик .NET Разработчик @netdeveloperdiary · 6.77K subscribers
Post #1216 1.42K
День девятьсот девяносто второй. #ЗаметкиНаПолях #AsyncTips
Отмена Группы Задач
Вчерашний пост вызвал в чате обсуждение. Возник такой вопрос: если одна из ожидаемых задач выбросит исключение, можно ли отменить остальные? Уточню вводные:
- задачи формально не связаны, но результат нужен или их всех, или никакого;
- задачи нужно выполнять параллельно.

Первой моей идеей было передать во все задачи один токен отмены, и в случае исключения отменять все задачи одним токеном. Проблема в том, что сам токен не позволяет инициировать отмену (он только реагирует на отмену), поэтому в задачи нужно передавать CancellationTokenSource. В итоге, поискав решение, я нашёл интересный пост на StackOverflow, кратким описанием которого хочу поделиться. Извините за однобуквенные имена, сокращал, как мог. Полный код по ссылке ниже.

Итак, у нас есть «хорошие» и «плохие» задачи (передаём в них время задержки delay для удобства тестирования):
async Task<object> DoAsync(string id, 
CancellationToken token, int delay)
{
await Task.Delay(delay, token);
return id;
}

async Task<object> BadAsync(string id,
CancellationToken token, int delay)
{
await Task.Delay(delay, token);
throw new Exception(id);
}

Следующий метод «оборачивает» задачу, перехватывая исключения в ней и вызывая отмену остальных:
async Task<T> Wrap<T>(Task<T> task,
CancellationTokenSource cts)
{
try {
return await task;
}
catch {
if (!cts.IsCancellationRequested)
cts.Cancel(); // отменяем остальные
throw;
}
}

Теперь собственно метод, выполняющий все задачи:
public async Task DoWorkAsync(CancellationToken t)
{
var tasks = new List<Task<object>>();
var cts = new CancellationTokenSource();
var ct = cts.Token;

try {
using (t.Register(() => cts.Cancel())) {
// параллельно запускаем задачи
tasks.Add(Wrap(DoAsync("1", ct, 500), cts));
tasks.Add(Wrap(DoAsync("2", ct, 1000), cts));
tasks.Add(Wrap(DoAsync("3", ct, 1500), cts));
tasks.Add(Wrap(BadAsync("Bad", ct, 700), cts));

await Task.WhenAll(tasks.ToArray());
}
}
catch (Exception e) {
Console.WriteLine(e.Message);
}

Console.WriteLine("Results:");
tasks.ForEach((task) =>
Console.WriteLine(task.Status));
}

Здесь мы используем внешний токен отмены t, чтобы иметь возможность отменить все задачи извне. В директиве using регистрируем действие, которое будет выполнено при отмене внешнего токена.
Далее создаём несколько задач, «оборачивая» их в наш метод Wrap. Передаём в них один токен отмены и разные времена задержки, имитируя асинхронную работу. Наша «плохая» задача выбросит исключение после задачи 1, но перед завершением задач 2 и 3, метод Wrap перехватит исключение и отменит задачи 2 и 3.

Запускаем:
var cts = new CancellationTokenSource(10000);
DoWorkAsync(cts.Token).Wait();

//Вывод (в порядке регистрации задач):
RanToCompletion
Canceled
Canceled
Faulted

Подробный код и обработка различных вариантов завершения группы задач по ссылке ниже.

Источник: https://stackoverflow.com/questions/19394150/tpl-execute-dependent-task-and-stop-all-task-when-some-task-fails#19397190
  • 👍 2
More from @netdeveloperdiary
  1. Oct 17, 2026День 2817. #ЧтоНовенького #VS Прекращаем Сборку После Первой Ошибки Мэдс Кристенсен продол…
  2. Oct 16, 2026День 2816. #Карьера #AI Станет ли ИИ-Кодинг Концом Программирования для Людей? Автор ориги…
  3. Oct 15, 2026Post #3374
  4. Oct 14, 2026Post #3373
  5. Oct 13, 2026День 2813. #ЗаметкиНаПолях #Async Ещё Раз Про Async/Await в C#. Часть 1 Почему существует…
  6. Oct 12, 2026День 2812. #Карьера #AI Как Разработчикам Оставаться Полезными, Когда Код Пишет ИИ? По мер…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →